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
Related
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]));
}
Maybe someone can help me
I am not familiar with symfony.
There is running Symfony 3.3.9 with Smarty 3.1.27
I want to inject something to the Session Handling, so each time session is started with
new Session() or
$session = $this->container->get('session');
different session values are given
for example
<?php
namespace AppBundle\Components;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
class MDSession extends Session {
private $domain = null;
private $mandant = null;
const DEFAULT_THEME = '_default';
public function __construct(SessionStorageInterface $storage = null,AttributeBagInterface $attributes = null,FlashBagInterface $flashes = null)
{
parent::__construct($storage, $attributes, $flashes);
$this->getDomain();
/**
* check if session is set and the same requested domain given
*/
if(!$this->_get('domain') || $this->_get('domain') != $this->domain)
{
$this->mandant = $this->getMandant();
/**
* set session here
*/
$this->_set('domain', $this->domain);
$this->_set('mandant', $this->mandant['id']);
$this->_set('theme', $this->mandant['theme']);
}
}
public function _set($name=null,$value=null)
{
parent::set($name,$value);
}
public function _get($name)
{
parent::get($name);
}
/**
* HostnameLookups must be set to On in Apache
*/
private function getDomain()
{
$this->domain = strtolower($_SERVER["HTTP_HOST"]);
}
private function getMandant()
{
/**
* do something here
*/
}
}
How to set config.yml or services.yml to get it working ?
at the moment I do it with an EventListener like this.
I hope this is the right way
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class SessionHandler
{
const DEFAULT_THEME = '_default';
const DEFAULT_MANDANT = '1';
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();
$host = $request->getHost();
if(!empty($session->get('mandant')) OR $session->get('host') != $host)
{
//check DB for mandant
//.....
//setting session
$session->set('host', $host);
$session->set('mandant', self::DEFAULT_MANDANT);
$session->set('theme', self::DEFAULT_THEME);
}
if (!$event->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
// ...
}
}
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.
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
Is there a way to get the name of the action in a Symfony2 controller?
public function createAction(Request $request, $title) {
// Expected result: create
$name = $this->getActionName();
}
useļ¼
$request->attributes->get('_controller');
// will get yourBundle\Controller\yourController::CreateAction
$params = explode('::',$request->attributes->get('_controller'));
// $params[1] = 'createAction';
$actionName = substr($params[1],0,-6);
// $actionName = 'create';
I found this snippet (here):
$matches = array();
$controller = $this->getRequest()->attributes->get('_controller');
preg_match('/(.*)\\\(.*)Bundle\\\Controller\\\(.*)Controller::(.*)Action/', $controller, $matches);
which seems to be a promising approach. This regexp actually doesn't work. But it won't be hard to fetch the action name by using strstr(). Works!
And returns (see example)
Array
(
[0] => Acme\MyBundle\Controller\MyController::myAction
[1] => Acme
[2] => My
[3] => My
[4] => my
)
If input was Acme\MyBundle\Controller\MyController::myAction.
Now, I am using this with Symfony 2.8, (and Symfony3):
<?php
namespace Company\Bundle\AppBundle\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Request as BaseRequest;
/**
* Request shortcuts.
*/
class Request extends BaseRequest
{
/**
* Extract the action name.
*
* #return string
*/
public function getActionName()
{
$action = $this->get('_controller');
$action = explode('::', $action);
// use this line if you want to remove the trailing "Action" string
//return isset($action[1]) ? preg_replace('/Action$/', '', $action[1]) : false;
return $action[1];
}
/**
* Extract the controller name (only for the master request).
*
* #return string
*/
public function getControllerName()
{
$controller = $this->get('_controller');
$controller = explode('::', $controller);
$controller = explode('\\', $controller[0]);
// use this line if you want to remove the trailing "Controller" string
//return isset($controller[4]) ? preg_replace('/Controller$/', '', $controller[4]) : false;
return isset($controller[4]) ? $controller[4] : false;
}
}
To use this custom request class, you must "use" it in your web/app*.php controllers:
use Company\Bundle\AppBundle\Component\HttpFoundation\Request;
// ...
$request = Request::createFromGlobals();
// ...
Then in your controller:
class AppController extends Controller
{
/**
* #Route("/", name="home_page")
* #Template("")
*
* #return array
*/
public function homePageAction(Request $request)
{
$controllerName = $request->getControllerName();
$actionName = $request->getActionName();
dump($controllerName, $actionName); die();
// ...
}
Will output:
AppController.php on line 27:
"AppController"
AppController.php on line 27:
"homePageAction"
You can also access these functions through the RequestStack service:
class MyService
{
/**
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function myService()
{
$this->controllerName = $this->requestStack->getMasterRequest()->getControllerName();
$this->actionName = $this->requestStack->getMasterRequest()->getActionName();
// ...
}
If you use Controller as a Service than the schema is different:
$request->attributes->get('_controller'); will return "service_id:createAction"
A possible solution for both schemas:
$controller = $request->attributes->get('_controller');
$controller = str_replace('::', ':', $controller);
list($controller, $action) = explode(':', $controller);
In all version of symfony and without $request or container, service or nothing else... , directly in your method
public function myMethod(){
$methodName = __METHOD__;
return $methodName;
}
// return App\Controller\DefaultController::myMethod
public function mySecondMethod(){
$methodName = explode('::', __METHOD__);
return $methodName[1];
}
// return mySecondMethod