Zend Expressive Route with optional parameter - zend-framework

I want use a route to get the complete collection and, if available, a filtered collection.
so my route:
$app->get("/companies", \App\Handler\CompanyPageHandler::class, 'companies');
My Handler for this route:
use App\Entity\Company;
use App\Entity\ExposeableCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CompanyPageHandler extends AbstractHandler
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$categories = new ExposeableCollection();
foreach (['test', 'test1', 'test3'] as $name) {
$category = new Company();
$category->setName($name);
$categories->addToCollection($category);
}
return $this->publish($categories);
}
}
When getting this route /companies, i get the expected collection
[{"name":"test"},{"name":"test1"},{"name":"test3"}]
So now i change the route
$app->get("/companies[/:search]", \App\Handler\CompanyPageHandler::class, 'companies');
It's all fine when i'm browsing to /companies.
But if i try the optional parameter /companies/test1 then i got an error
Cannot GET http://localhost:8080/companies/test1
my composer require section:
"require": {
"php": "^7.1",
"zendframework/zend-component-installer": "^2.1.1",
"zendframework/zend-config-aggregator": "^1.0",
"zendframework/zend-diactoros": "^1.7.1 || ^2.0",
"zendframework/zend-expressive": "^3.0.1",
"zendframework/zend-expressive-helpers": "^5.0",
"zendframework/zend-stdlib": "^3.1",
"zendframework/zend-servicemanager": "^3.3",
"zendframework/zend-expressive-fastroute": "^3.0"
},
In Zend Framework 2 and Symfony4 this route definition works fine. So im confused.
Why my optional parameter doesn't work?

That's because you are using https://github.com/nikic/FastRoute router and correct syntax would be:
$app->get("/companies[/{search}]", \App\Handler\CompanyPageHandler::class, 'companies');
or be more strict and validate search param something like this:
$app->get("/companies[/{search:[\w\d]+}]", \App\Handler\CompanyPageHandler::class, 'companies');

Related

SuiteCRM v7.10.29 Custom API v4.1

I am creating a custom API for SuiteCRM. When I attempt to run the new API from {CRM Home}/custom/service/v4_1_custom I receive an 'HTTP ERROR 500'. There are not errors in the error_log file or the SuiteCRM.log file.
I have followed the method in the following two url's
https://fayebsg.com/2013/05/extending-the-sugarcrm-api-updating-dropdowns/
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_10.0/Integration/Web_Services/Legacy_API/Extending_Web_Services/
registry.php
<?php
require_once('service/v4_1/registry.php');
class registry_v4_1_custom extends registry_v4_1
{
protected function registerFunction()
{
parent::registerFunction();
$this->serviceClass->registerFunction('test', array(), array());
}
}
SugarWebServicesImplv4_1_custom.php
<?php
if(!defined('sugarEntry'))define('sugarEntry', true);
require_once('service/v4_1/SugarWebServiceImplv4_1.php');
class SugarWebServiceImplv4_1_custom extends SugarWebServiceImplv4_1
{
/**
* #return string
*/
public function test()
{
LoggerManager::getLogger()->warn('SugerWebServiceImplv4_1_custom test()');
return ("Test Worked");
} // test
} // SugarWebServiceImplv4_1_custom
I found the answer to this issue.
In the file {SuiteCRM}/include/entryPoint.php there are many files that are included thru require_once. In this list of require_once files, there were 4 files that were set as require not require_once. These were classes and therefore could not be included a second time. I changed these to require_once and the HTTP Error 500 went away and the custom APIs started working.

Test a Symfony REST API using Behat / Mink : prb with POST request

My challenge here is to find the best way to test a Symfony (3.4) API application using Behat/Mink for functionnal test, in my CICD platform.
Because my testing processes must be called in a shell script, all the tests must be very linear. I have no way to start a standalone webserver like Apache or the PHP/Symfony webserver. Also, Docker is not an option.
For the moment, I can successfully test the GET verbs of the API using the Mink syntax :
-- file test.feature
#function1
Scenario Outline: Test my api
When I go to "/api/v1/hello"
Then the response is JSON
The "I go to" instruction is implemented by Mink (http://docs.behat.org/en/v2.5/cookbook/behat_and_mink.html) and it emulates a GET request only. When this instruction is called by BeHat, the app Symfony kernel is "spawned" and the "api/v1/hello" method is called internally : there is no network trafic, no TCP connection, there is no need for a dedicated webserver (apache, or the symfony standalone server). It looks like Behat is emulating a webserver and start by itself the Symfony app it its own user space.
Now I want to test the POST verbs of my API, with a json payload, but unfortunally Mink do not have other verbs than GET.
I have read some articles over the web (keyword : behat test post api) but all I have seen is based on a Guzzl/Curl client. So a real client-to-server connection is made to http://localhost and a real webserver have to respond to the request.
I want the Symfony API to be called internally without using an other webserver.
Is there a way to do that ? How to test a Symfony REST API and specially the POST verb without needing a standalone server to reply ?
Thank you.
Here is how I do a functional test of a POST API, with BeHat, without a local running webserver :
test.feature :
#function1
Scenario Outline: Test my api
Given I have the payload
"""
{ "data":"object"}
"""
When I request "POST /api/v1/post"
Then the response is JSON
The featureContext file implement two functions :
"I Have The Payload" : See here https://github.com/philsturgeon/build-apis-you-wont-hate/blob/master/chapter8/app/tests/behat/features/bootstrap/FeatureContext.php
"I request" : based on code provided by philsturgeon just above, I modify it to have something like that :
/**
* #When /^I request "(GET|PUT|POST|DELETE|PATCH) ([^"]*)"$/
*/
public function iRequest($httpMethod, $resource)
{
$this->lastResponse = $this->lastRequest = null;
$this->iAmOnHomepage();
$method = strtoupper($httpMethod);
$components = parse_url($this->getSession()->getCurrentUrl());
$baseUrl = $components['scheme'].'://'.$components['host'];
$this->requestUrl = $baseUrl.$resource;
$formParams = json_decode($this->requestPayload, true);
$formParamsList = [];
foreach($formParams as $param => $value) {
$formParamsList[$param] = json_encode($value);
}
// Construct request
$headers = [
'Accept'=>'application/json',
'Content-Type'=>'application/x-www-form-urlencoded'
];
try {
// Magic is here : allow to simulate any HTTP verb
$client = $this->getSession()->getDriver()->getClient();
$client->request(
$method,
$this->requestUrl,
$formParamsList,
[],
$headers,
null);
} catch (BadResponseException $e) {
$response = $e->getResponse();
// Sometimes the request will fail, at which point we have
// no response at all. Let Guzzle give an error here, it's
// pretty self-explanatory.
if (null === $response) {
throw $e;
}
$this->lastResponse = $e->getResponse();
throw new \Exception('Bad response.');
}
}
If you use Mink then it is quite easy
class FeatureContext extends RawMinkContext
{
/**
* #When make POST request to some Uri
*/
public function makePostRequestToSomeUri(): void
{
$uri = '/some-end-point';
/** #var \Symfony\Component\BrowserKit\Client $client */
$client = $this->getSession()->getDriver()->getClient();
$postParams = [];
$files = [];
$serverParams = [];
$rawContent = '';
$client->request(
\Symfony\Component\HttpFoundation\Request::METHOD_POST,
$uri,
$postParams,
$files,
$serverParams,
$rawContent
);
/** #var \Symfony\Component\HttpFoundation\Response $response */
$response = $client->getResponse();
//...
}
}

All Dingo routes return 404 when phpunit testing

original posted at https://github.com/dingo/api/issues/1472
I'm using Lumen 5.1 and DingoApi 1.0.x to do my api development, and now I'm trying to do some acceptance testing. Following the documentation of Lumen, here is how I do it:
Here is a simplified routes definition in app\Http\routes.php:
$app->get('/', function () use ($app) {
return "Welcome to mysite.com";
});
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function ($api) {
$api->group([
'prefix' => 'dealer',
'middleware' => 'checkH5ApiSign'
], function ($api) {
$api->get('list', 'App\Http\Controllers\Credit\DealerController#index');
$api->get('staff_list', 'App\Http\Controllers\Credit\DealerController#getStaffList');
});
}
I can access both routes defined using $app or $api(dingo) in browser or via postman, they both can return a 200 response. But whenever I'm trying to access those routes in phpunit, the $app defined route like / is responding okay with 200 code, but all routes defined with $api(dingo) will response with 404 status code. Here is my test code:
class DealerTest extends TestCase
{
public function testTest()
{
$this->get('/')->assertResponseOk();
$this->get('/dealer/list')->assertResponseOk();
$this->get('/dealer/staff_list')->assertResponseOk();
}
}
and ran result:
PHPUnit 5.7.5 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 590 ms, Memory: 6.00MB
There was 1 failure:
1) DealerTest::testTest
Expected status code 200, got 404.
Failed asserting that false is true.
E:\Gitrepos\api.fin.youxinjinrong.com\vendor\laravel\lumen-framework\src\Testing\AssertionsTrait.php:19
E:\Gitrepos\api.fin.youxinjinrong.com\tests\DealerTest.php:8
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
I tried ran through Dingo package code to find the cause, but failed. All other related issue could not solve my problem either. So please help me.
update
I followed the code flow, and see that FastRoute\DataGenerator\RegexBasedAbstract.php is doing the addRoute() operation, I dumped $this->staticRoutes) in that addRoute() method, see that it's doing okay both inside browser and under phpunit. But weird enough, the following call of ->getData() is behaving differenctly: in browser all static routes are returned, but not in phpunit.
Hope this can somehow be helpful. I'm still digging this problem...
So I got mine to work by doing this;
Using the example in the example used in creating the issue:
class DealerTest extends TestCase
{
public function testTest()
{
$this->get('/')->assertResponseOk();
$this->get('/dealer/list')->assertResponseOk();
$this->get('/dealer/staff_list')->assertResponseOk();
}
}
becomes
class DealerTest extends TestCase
{
public function testTest()
{
$this->get(getenv('API_DOMAIN') . '/v1/')->assertResponseOk();
$this->get(getenv('API_DOMAIN') . '/v1/dealer/list')->assertResponseOk();
$this->get(getenv('API_DOMAIN') . '/v1/dealer/staff_list')->assertResponseOk();
}
}
I hope this helps

How to fix error "Cannot dispatch middleware Application\Middleware\IndexMiddleware"?

I've set up a Zend Application as normal, except in my case the difference is that I set it up over an existing legacy web application.
I still want to call my existing legacy application over the ZF3 app. It was suggested I can do so using Middleware. I went over https://docs.zendframework.com/zend-mvc/middleware/ and set up my routing as described there.
However, when I run the application, I am greeted by this:
Cannot dispatch middleware Application\Middleware\IndexMiddleware
#0 zend-mvc\src\MiddlewareListener.php(146):
Zend\Mvc\Exception\InvalidMiddlewareException::fromMiddlewareName('Application\\Mid...')
Here is where the exception happens:
https://github.com/zendframework/zend-mvc/blob/release-3.1.0/src/MiddlewareListener.php#L146
Just to note:
$middlewareToBePiped; //'Application\Middleware\IndexMiddleware'
is_string($middlewareToBePiped); // true
$serviceLocator->has($middlewareToBePiped);//false
$middlewareToBePiped instanceof MiddlewareInterface; //false
is_callable($middlewareToBePiped);//false
My class is:
namespace Application\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Zend\Http\Response;
class IndexMiddleware implements MiddlewareInterface
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
{}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{}
}
I am thinking that my issue is that my IndexMiddleware class is not being found in ServiceLocator... (line 142 of linked API). How do I get it in there?
I put this into my application.config.php file:
'service_manager' => [
'invokables' => array(
'middleware' => IndexMiddleware::class
)
]
onto the next error it is.. (Last middleware executed did not return a response.)
but looks like it has executed)

Symfony2 - change security entry point

Using symonfy as a REST API, I would like the server not to send such headers on a 401 :
WWW-Authenticate : Basic realm=XXX
but something like
WWW-Authenticate : myOwnBasic realm=XXX
How can I overload the BasicAuthenticationEntryPoint class or make my own entry point class for basic auth ?
I finally found the solution :
You need to override this paramater in parameters.yml :
security.authentication.basic_entry_point.class: NAMESAPCE\YOURCUSTOM_CLASS
And create a file where you prefer (I made it in MyBundle\Security\Http\EntryPoint) looking like :
<?php
namespace NAMESAPCE;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint;
class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint
{
private $realmName;
public function __construct($realmName)
{
$this->realmName = 'XXX';
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('myOwnBasic realm="%s"', $this->realmName));
$response->setStatusCode(401);
return $response;
}
}