How to get access to $app inside a controller as the Slim 3.3 injects only the ContainerInterface?
Code to illustrate the question:
$app = new \Slim\App;
$app->get('/home', 'HomeController:get');
$app->run();
class HomeController {
private $ci;
public function _construct($ci) {
$this->ci = $ci;
}
public function get($request, $response) {
$this->ci->get(...);
// How to access $app and dependencies like $app->jwt?
}
}
This was a tough one.
Slim 3 heavily uses dependency injection, so you might want to use it too.
First inside your dependencies.php you need to grab the $app and throw it in a container to inject it to the Controller later.
$container['slim'] = function ($c) {
global $app;
return $app;
};
Then you got to inject it:
// Generic Controller
$container['App\Controllers\_Controller'] = function ($c) {
return new _Controller($c->get('slim'));
};
Now on your controller.php:
private $slim;
/**
* #param \Psr\Log\LoggerInterface $logger
* #param \App\DataAccess $dataaccess
* #param \App\$app $slim
*/
public function __construct(LoggerInterface $logger, _DataAccess $dataaccess, $slim)
{
$this->logger = $logger;
$this->dataaccess = $dataaccess;
$this->slim = $slim;
}
Now you just got call it like this:
$this->slim->doSomething();
You can make your own 'singleton' to mimic Slim::getInstance(); ;)
class Anorexic extends \Slim\App {
private static $_instance;
public static function getInstance(){
if(empty(self::$_instance){
self::$_instance = new self();
}
return self::$_instance;
}
}
Then change your initialization like this:
// $app = new \Slim\App;
$app = Anorexic::getInstance();
Now you can get your \Slim\App instance anywhere in your code by calling Anorexic::getInstance(); Ofcourse you should never try this at home :P
Related
Was testing an application and was repeatable getting the infamous new entity not configured to cascade persist error. I was surprised since I wasn't even creating new entities, and after digging into it, it appears to be relate to using different instances of the EntityManager object (I have confirmed that they are working with the same database, however) which I guess makes sense since each test will have a transaction applied. The only way I was able to get rid of the errors was to use the entityManager in the container instead of the autowired ones. While it works, it is a bit of a kludge and I would like to know the right way of doing this. Thank you
namespace App\Tests;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
class MyTest extends ApiTestCase
{
/**
* #dataProvider getData
*/
public function testWhichDoesNotWork(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
/**
* #dataProvider getData
*/
public function testWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $service->getUser();
$randomId = $service->getRandomEntityId($user->getTenant(), $class);
$randomEntity = $em->getRepository($class)->find($randomId);
$randomEntity->setSomething('something');
$em->persist($randomEntity);
$em->flush();
}
/**
* #dataProvider getData
*/
public function testAnotherWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$service->setNewEntityManager(static::getContainer()->get(EntityManagerInterface::class));
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
public function getData(): array
{
return [
[123, SomeClass::class]
];
}
}
namespace App\Test\Service;
final class MyService
{
public function __construct(private EntityManagerInterface $entityManager)
{}
public function setNewEntityManager(EntityManagerInterface $entityManager):self
{
$this->entityManager = $entityManager;
return $this;
}
public function getDatabase():string
{
return $this->entityManager->getConnection()->getDatabase();
}
public function getUser(int $id):User
{
return $this->entityManager->getRepository(User::class)->find($id);
}
public function getRandomId(Tenant $tenant, string $class):int
{
$meta = $this->entityManager->getClassMetadata($class);
$_sql = 'SELECT %s FROM public.%s WHERE tenant_id=? OFFSET floor(random() * (SELECT COUNT(*) FROM public.%s WHERE tenant_id=?)) LIMIT 1;';
$sql = sprintf($_sql, $meta->getSingleIdentifierFieldName(), $meta->getTableName(), $meta->getTableName());
return $this->entityManager->getConnection()->prepare($sql)->execute([$tenant->getId(), $tenant->getId()])->fetchOne();
}
public function getRandomEntity(Tenant $tenant, string $class):object
{
return $this->entityManager->getRepository($class)->find($this->getRandomId($tenant, $class));
}
public function saveEntity(object $entity):self
{
$this->entityManager->persist($entity);
$this->flush();
return $this;
}
}
services:
app.test.my.service:
alias: App\Test\Service\MyService
public: true
Im using an interface to interact with a class and its methods. How can I pass parameters (user credentials) to the constructor in this class? Ive only managed to make it work by hard coding some parameters in the "Wrapper". Then calling the methods works as it should.
namespace App\Http;
interface WrapperInterface
{
public function __construct(string $id, $secret);
}
class Wrapper implements WrapperInterface
{
public function __construct($id = '', $secret = '')
{
$this->id = $id;
$this->secret = $secret;
$this->curlInit();
}
public function SomeFunction()
{
// some functionality
return $this->SomePrivateFunction();
}
private function SomePrivateFunction()
{
// some functionality
}
}
use App\Http\WrapperInterface;
class SomeCommand
{
private $client
public function __construct(WrapperInterface $client)
{
$this->client = $client;
parent::__construct();
}
public function handle()
{
$this->client->someFunction();
}
}
All of your code is syntactically incorrect.
Classes don't extend interfaces, they implement them.
Classes must implement interface method prototypes exactly.
Class SomeCommand has no parent, so you can't call parent::anything unless you truly want the generic Object class's blank methods/properties.
I've never seen a constructor defined in an interface and it's probably not a good idea, but it doesn't seem to be forbidden, so... fill your boots.
It looks like you want to do composition, which would look like:
interface WrapperInterface {
public function __construct(string $id, $secret);
}
class Wrapper implements WrapperInterface {
protected $id, $secret;
public function __construct(string $id, $secret) {
$this->id = $id;
$this->secret = $secret;
}
public function debug() {
return [ $this->id, $this->secret ];
}
}
class SomeCommand {
private $client;
public function __construct(WrapperInterface $client) {
$this->client = $client;
}
public function debug() {
return $this->client->debug();
}
}
$i = new Wrapper('someid', 'somesecret');
$c = new SomeCommand($i);
var_dump( $c->debug() );
and is actual, runnable code: https://3v4l.org/9L2Uo
Is there a way to use a object variable instantiated from a class in two functions?
Here's the code I've tried, but its just returning null:
class bookAppointmentsController extends APIController
{
private $business;
public funcition check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
}
public function book()
{
dd($this->business) //returns null
$this->business->book();
}
}
I am trying to use the $business object in two functions but it does not work, when I dd($business) it returns null
Any way to do this?
Move the instantiation to the constructor:
public function __construct(APIClass $business)
{
$this->business = $business;
}
However, it would be better if you make Laravel do the heavy lifting and prepare the APIClass for you.
In your AppServicePorvider under the register method, you can create the APIClass
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind('APIClass', function ($app) {
$api = new APIClass();
// Do any logic required to prepare and check the api
$key = config('API_KEY');
$api->connectAPI($key);
return $api;
});
}
Check the documentations for more details.
Maybe the solution could be to make the variable Global
You could make the variable global:
function method( $args ) {
global $newVar;
$newVar = "Something";
}
function second_method() {
global $newVar;
echo $newVar;
}
Or you could return it from the first method and use it in the second method
public function check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
return $this->business;
}
public function book()
{
$business = check($key);
$business->book();
}
How can I test a forward in a controller with PHPUnit?
I have two simple modules (A and B), module A call the module B using a forward.
here is a simple code that not work :
ModuleA
class ModuleAController extends AbstractRestfulController
{
protected $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->em;
}
public function getList()
{
$data = array('message' => 'passed by module A');
$forward = $this->forward()->dispatch('ModuleB\Controller\ModuleB');
$data['Message'] = $forward->getVariable('Message');
return new JsonModel($data);
}
}
ModuleB
class ModuleBController extends AbstractRestfulController
{
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
}
public function getList()
{
$data = array('Message'=>'passed by module B');
return new JsonModel($data);
}
}
And this is a test code :
class ModuleAControllerTest extends AbstractHttpControllerTestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new ModuleAController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array());
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testModuleAControllerCanBeAccessed()
{
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, 1+99+100);
}
}
And this is the error message :
There was 1 error:
1) ModuleATest\Controller\ModuleAControllerTest::testModuleAControllerCanBeAccessed
Zend\ServiceManager\Exception\ServiceNotCreatedException: An exception was raised while creating "forward"; no instance returned
....
Caused by
Zend\ServiceManager\Exception\ServiceNotCreatedException: Zend\Mvc\Controller\Plugin\Service\ForwardFactory requires that the application service manager has been injected; none found
....
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
Is there any way to make this code work ??Any idea ??
Thank you.
I have not created mock for plugins yet. I don't know how set new plugin to controller. But mock will be like it.
PHPunit test file
public function testControllerWithMock()
{
/* Result from ModuleBController method getList() */
$forwardResult = new JsonModel(array('Message'=>'passed by module B'));
/* Create mock object for forward plugin */
$forwardPluginMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\Forward')
->disableOriginalConstructor()
->getMock();
$forwardPluginMock->expects($this->once())
->method('dispatch') /* Replace method dispatch in forward plugin */
->will($this->returnValue($forwardResult)); /* Dispatch method will return $forwardResult */
/* Need register new plugin (made mock object) */
$controller->setPluginManager(); /* ??? Set new plugin to controller */
I'm thinking how decide it.
Ok, try it.
$controller->getPluginManager()->injectController($forwardPluginMock);
I don't write PHPUnit tests for controllers. Because controllers must return a view and best solution using Selenium for testing view. I usually use PHPUnitSelenium tests for testing it.
I started with Doctrine2 usage in my projects. Howevery, I don't understand something. Normally, I am working with PHP classes and my problem is:
require_once 'bootstrap.php';
class test {
public function addEmployee($name, $lastname) {
$emp = new Employee();
$emp->name = $name;
... other code
$entityManager->persist($emp);
$entityManager->flush();
}
}
Gives error that entuty manager is noc declared as variable. But, when I include bootstrap.php in function, it works. Like this:
class test {
public function addEmployee($name, $lastname) {
require_once 'bootstrap.php';
$emp = new Employee();
$emp->name = $name;
... other code
$entityManager->persist($emp);
$entityManager->flush();
}
}
I think it will be really slow if I include that in each function, so my question is: Is there any other way to include 'bootstrap.php' for all functions in class?
Use dependency injection. For example
class Test {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function __construct(\Doctrine\ORM\EntityManager $entityManager) {
$this->em = $entityManager;
}
public function addEmployee($name, $lastname) {
// snip
$this->em->persist($emp);
$this->em->flush();
}
}
require_once 'bootstrap.php';
$test = new Test($entityManager);