Zend + Soap Server + Doctrine, Error with serviceLocator/serviceManager - php

I'm trying to expose some data with soap.
here's my controller holding the server (everything is normal here):
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Json\Json;
use Zend\Soap\Server;
use Zend\Soap\AutoDiscover;
class ExportController extends AbstractActionController
{
private $_options = array('soap_version' => SOAP_1_2);
private $_URI = '/export';
private $_WSDL_URI = '/export?wsdl';
private $wsdl;
public function indexAction() {
if (isset($_GET['wsdl'])) {
$this->handleWSDL();
} else {
$this->handleSOAP();
}
return $this->getResponse();
}
private function handleWSDL() {
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$autodiscover = new AutoDiscover(new \Zend\Soap\Wsdl\ComplexTypeStrategy\ArrayOfTypeSequence());
$autodiscover->setClass('Application\WebService\ExportClass')
->setUri($serverUrl.$this->_URI)
->setServiceName('MySoapService');
$autodiscover->handle();
$this->wsdl = $autodiscover->generate();
}
private function handleSOAP() {
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$soap = new Server($serverUrl.$this->_WSDL_URI, $this->_options);
$soap->setClass('Application\WebService\ExportClass');
$soap->handle();
}
}
then here is the class I'm exporting:
namespace Application\WebService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceManagerInterface;
use Doctrine\ORM\EntityManager;
use Zend\Json\Json;
use Parcours\Entity\Parcours;
class ExportClass implements ServiceLocatorAwareInterface
{
protected $em;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
$this->em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
return $this->em;
}
/**
* Dit bonjour!
*
*
* #return string
*/
public function helloWorld(){
return 'coucou';
}
/**
* Retourne le titre d'un parcours
*
* #param integer $id
* #return array
*/
public function getParcours($id){
$parcours = $this->getEntityManager()->getRepository('Parcours\Entity\Parcours')->findOneBy(array('id'=>$id));
return $parcours->toArray();
}
}
I also have a test client, the first function: helloWorld() is working fine but the second one: getParcours($id) is returning the following error:
Call to a member function get() on a non-object
It seams like getServiceLocator() is returning null. I'm using a similar piece of code an AbstractActionController: ParcoursController which is working great. Why can't I do that here?
[EDIT]
Ok I've tried something else, instead of using the EntityManager in the ExportClass I've made a get function in my ParcoursController and call this function into the ExportClass. My ParcoursController is already using the EntityManager to display my data into pages so it should work. But the result is the same.
It seems like i should somehow pass my serviceLocator through the SOAP service. I don't think that's a good idea.

OK great I nailed it.
Here is my working conf, hope it helps someone.
All changes from the above example were:
A: added this to module.php (ExportModel is ExportClass from last example i just changed name and namespace)
return array(
'invokables' => array(
'Application\Model\ExportModel' => 'Application\Model\ExportModel',
),
)
B: I gave the instanciated model to my SoapServer
private function handleSOAP() {
$exportModel = $this->getServiceLocator()->get('Application\Model\ExportModel');
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$soap = new Server($serverUrl.$this->_WSDL_URI, $this->_options);
$soap->setClass('Application\Model\ExportModel');
$soap->setObject($exportModel);
$soap->handle();
That's all.

Related

Symfony 3 unit test form with constructor

I am trying to unit test a form which has 2 dependencies (ObjectManager and EventDispatcher)
I had tried to follow official doc but without success.
My testing file:
<?php
namespace Lch\MediaBundle\Tests\Form;
use Lch\MediaBundle\Form\AddImageType;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class AddImageTypeTest extends TypeTestCase
{
private $entityManager;
private $eventDispatcher;
protected function setUp()
{
$this->entityManager = $this->createMock(ObjectManager::class);
$this->eventDispatcher = $this->createMock(EventDispatcher::class);
parent::setUp();
}
protected function getExtensions()
{
$type = new AddImageType($this->entityManager, $this->eventDispatcher);
return array(
new PreloadedExtension(array($type), array()),
);
}
public function testSubmitValidData()
{
$form = $this->factory->create(AddImageType::class);
}
}
I got this error when I execute my test suite:
TypeError: Argument 1 passed to
LCH\MediaBundle\Form\AddImageType::__construct() must implement
interface Doctrine\Common\Persistence\ObjectManager, none given,
called in
/home/matthieu/www/lch/media/src/Lch/MediaBundle/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php
on line 85
It seems that the job I do in the getExtensions method is not working, but cannot figure it out.
Does anyone have a clue?
ObjectManager is an interface, meaning you can't instantiate or pass it directly to other constructors.
If you are using Doctrine, replace it with Doctrine\ORM\EntityManager which implements ObjectManager interface and can be instantiated, otherwise replace it with your own implementation.
<?php
namespace Lch\MediaBundle\Tests\Form;
use Lch\MediaBundle\Form\AddImageType;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class AddImageTypeTest extends TypeTestCase
{
private $entityManager;
private $eventDispatcher;
protected function setUp()
{
$this->entityManager = $this->createMock(EntityManager::class);
$this->eventDispatcher = $this->createMock(EventDispatcher::class);
parent::setUp();
}
protected function getExtensions()
{
$type = new AddImageType($this->entityManager, $this->eventDispatcher);
return array(
new PreloadedExtension(array($type), array()),
);
}
public function testSubmitValidData()
{
$form = $this->factory->create(AddImageType::class);
}
}

Slim3 Container good practice?

Hello im learning PHP and i'am Building a REST API with the Slim3 Framework. I Create Routes Like this:
$container['HomeController'] = function () {
return new HomeController();
};
$currentContainer = CurrentContainer::getInstance();
$currentContainer->setContainer($container);
$app->get('/', 'HomeController:index')->setName("index");
My Problem was i had to pass the $container to every Single Controller Class iv'e created, because i need the container context in the Controller for routing etc.
then im build a Singleton Container Class like this:
class CurrentContainer
{
private static $instance;
private $container;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (self::$instance == null) {
self::$instance = new CurrentContainer();
}
return self::$instance;
}
public function setContainer($container)
{
$this->container = $container;
}
/**
* #return mixed
*/
public function getContainer()
{
return $this->container;
}
}
so now its possible to create a "MainController" like this:
class Controller
{
/**
* #var mixed
*/
protected $view;
/**
* #var
*/
protected $router;
public function __construct()
{
$container = CurrentContainer::getInstance()->getContainer();
$this->view = $container->view;
$this->router = $container->router;
}
}
now all of my Controllers extends from the Controller class...
my question is now ... its that a good idea or is there a reason to not do it like that?
im thankful for every input
I've built some APIs with Slim Framework, and also tried so many method to get it done (of course in right way). I implemented MVC pattern on Slim Framework. The code example below:
For the controller, I created a base controller that injected with container. So the code:
<?php
namespace App\Controller;
use Slim\Container;
class Controller
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function __get($name)
{
return $this->container->get($name);
}
}
I loaded the base controller on dependencies container.
<?php
// controller
$container['controller'] = function ($c) {
return new App\Controller\Controller($c);
};
So I can get the container from the controller.
<?php
namespace App\Controller;
use App\Controller\Controller;
use Slim\Http\Request;
use Slim\Http\Response;
class HomeController extends Controller
{
public function __invoke(Request $request, Response $response, $args)
{
return $this->renderer->render($response, 'home');
}
}
I hope it helps.

Can I read input from GET inside a Controller Factory?

This question is not explicitly about ZF2, but I often take ques from ZF2 for my code. That said, most ZF2 examples I have seen process input inside a Controller Action.
Example:
class YourController extends AbstractActionController
{
public function doStuffAction()
{
// ZF2's way to get input from $_GET variable
$product = $this->getEvent()->getRouteMatch()->getParam('product');
// Process
$processor = (new ProcessorFactory())->getProcessor($product);
$output = $processor->processInput($data);
}
}
Now, I would like to inject a Processor into my Controller. Not create it inside the controller like I am doing above. But since Processor depends on knowing the $product, which is only gotten from $_GET, I do not see any other way.
If I want to inject Processor into Controller, I have to move the line that populates $product variable outside of the Controller as well.
How can I do so without breaking OOP, ZF2, design patterns badly? As in, I am under the impression that anything to do with $_GET is to be done inside a Controller, and not inside a ControllerFactory. Unless perhaps I can break this pattern?
If you just want to apply the Dependency Inversion principle. Applying the D of SOLID acronym, only a few changes are needed.
class YourController
{
/**
* #var ProcessorFactory
*/
protected $processorFactory;
public function __construct(ProcessorFactory $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function doStuffAction()
{
$product = $this->getEvent()->getRouteMatch()->getParam('product');
$processor = $this->processorFactory->getProcessor($product);
}
}
You could improve by typehinting to an Interface (SOLID)
class YourController
{
/**
* #var ProcessorFactoryInterface
*/
protected $processorFactory;
public function __construct(ProcessorFactoryInterface $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function doStuffAction()
{
$product = $this->getEvent()->getRouteMatch()->getParam('product');
$processor = $this->processorFactory->getProcessor($product);
}
}
Now, if you want don't want your Controller to be responsible of initiating the creating process (SOLID), you can split it up some more.
class YourController
{
/**
* #var ProcessorInterface
*/
protected $processor;
public function __construct(ProcessorInterface $processor)
{
$this->processor = $processor;
}
public function doStuffAction()
{
$processor = $this->processor;
}
}
class ControllerFactory
{
/**
* #var ProcessorFactory
*/
protected $processorFactory;
public function __construct(ProcessorFactory $processorFactory)
{
$this->processorFactory = $processorFactory;
}
public function create()
{
return new YourController($this->processorFactory->getProcessor());
}
}
class ProcessorFactory
{
/**
* #var RouteMatch
*/
protected $routeMatch;
public function __construct(RouteMatch $routeMatch)
{
$this->routeMatch = $routeMatch;
}
public function getProcessor()
{
$processor = $this->createProcessor();
// do stuff
return $processor;
}
protected function createProcessor()
{
$product = $this->routeMatch->getParam('product');
// create processor
return $processor;
}
}
The following code would get you your controller.
$controllerFactory = new ControllerFactory(new ProcessorFactory(new RouteMatch()));
$yourController = $controllerFactory->create();
Now above code is more general code and not adapted for ZF2. A good move would then to involve the ZF2's servicemanager.
class YourController extends AbstractActionController
{
/**
* #var ProcessorInterface
*/
protected $processor;
public function __construct(ProcessorInterface $processor)
{
$this->processor = $processor;
}
public function doStuffAction()
{
$processor = $this->processor;
}
}
class YourControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$services = $controllers->getServiceLocator();
$processorFactory = $services->get('ProcessorFactory');
return new YourController($processorFactory->getProcessor());
}
}
class ProcessorFactory
{
/**
* #var RouteMatch
*/
protected $routeMatch;
public function __construct(RouteMatch $routeMatch)
{
$this->routeMatch = $routeMatch;
}
public function getProcessor()
{
$processor = $this->createProcessor();
// do stuff
return $processor;
}
protected function createProcessor()
{
$product = $this->routeMatch->getParam('product');
// create processor
return $processor;
}
}
class ProcessorFactoryFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $services)
{
return new ProcessorFactory($services->get('RouteMatch'));
}
}
Above services/controllers and their factories should be registered with their ServiceManager/ControllerManager
$config = [
'controllers' = [
'factories' [
'YourController' => 'YourControllerFactory',
],
],
'service_manager' = [
'factories' [
'ProcessorFactory' => 'ProcessorFactoryFactory',
],
],
];
When a request gets dispatch to YourController, the ControllerManager returns a YourController instance with a Processor injected. Which Processor it gets depends on the request (a parameter inside RouteMatch).

getServiceLocator only works with LoginController

I am using ZF2 with Doctrine 2 ORM, using the following:
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
seems to work in LoginController but doesn't seem to work in any other controller, what might be the reasons ? ( All controllers are under the same application )
<?php
namespace OEC;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\Mvc\MvcEvent;
use Zend\ServiceManager\ServiceManager;
class Module implements AutoloaderProviderInterface
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig(){
return include __DIR__ . '/config/module.config.php';
}
public function onBootstrap(MvcEvent $e){
$e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractController', 'dispatch', function($e) {
$controller = $e->getTarget();
if ($controller instanceof Controller\LoginController) {
$controller->layout('login/login.phtml');
} else {
$controller->layout('layout/layout.phtml');
}
}, 100);
}
public function getServiceConfig(){
return array(
'factories' => array(
'Zend\Authentication\AuthenticationService' => function($serviceManager) {
return $serviceManager->get('doctrine.entitymanager.orm_default');
}
)
);
}
}
?>
Constantly getting this error Call to a member function get() on null
My working controller
<?php
namespace OEC\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Session\Container;
use OEC\Controller\CommonController;
class LoginController extends AbstractActionController{
public function indexAction(){
$session = self::checkUserLoginAction();
if( $session ){
return $this->redirect()->toUrl( MAIN_URL . 'oec/view/dashboard');
}
return new ViewModel();
}
public function loginAction(){
$username = $this->getRequest()->getPost('username');
$password = $this->getRequest()->getPost('password');
try{
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$user = $objectManager->getRepository('OEC\Entity\User')->findOneBy(array('username' => $username, 'password' => md5($password) ));
if( !$user ){
print_r("not found");exit;
}
if( $user && $user->getId() != 0 ){
$session = new Container('user');
$session->userId = $user->getId();
$session->username = $user->getUsername();
$session->userType = $user->getUserTypeId();
CommonController::json(true, "successfully logged in", NULL, "view/dashboard");exit;
}
}catch(\Doctrine\ORM\NoResultException $e) {
print_r("error");exit;
}
}
public static function checkUserLoginAction(){
$session = new Container('user');
if( $session->offsetExists('username') && $session->offsetExists('userId') && $session->offsetExists('userType') ){
return array(
"username" => $session->offsetGet('username'),
"name" => $session->offsetGet('name'),
"userType" => $session->offsetGet('userType')
);
}else{
return false;
}
}
public function logoutAction(){
$session = new Container('user');
$session->getManager()->destroy();
return $this->redirect()->toUrl( MAIN_URL . 'oec/view/login');
}
}
My not working controller
<?php
namespace OEC\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class BloodTypeController extends AbstractActionController
{
public function indexAction()
{
return new ViewModel();
}
public function listAll(){
$objectManager = $this->getServiceLocator()->get('doctrine');
$bloodRep = $objectManager->getRepository('OEC\Entity\Blood');
$bloods = $bloodRep->findAll();
$bloodsArr = array();
foreach( $bloods as $blood ){
array_push($bloodsArr , $blood->toArray() );
}
$objectManager->close();
return $bloods;
}
}
doctrine is not a valid name to get an entity manager, there are two ways to get it directly through the service manager:
$this->getServiceLocator()->get("Doctrine\ORM\EntityManager");
as you did in your LoginController will trigger the standard factory and always returns the entity manager orm_default
$this->getServiceLocator()->get("doctrine.entitymanager.orm_default");
will trigger an abstract factory and returns the entity manager orm_default or a different entity manager if you replace orm_default after the last dot. This is the most common way to get the entity manager.
You use 3 different ways to get your service (the EntityManager instance):
$serviceManager->get('doctrine.entitymanager.orm_default');
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$objectManager = $this->getServiceLocator()->get('doctrine');
This third one is wrong. You are getting the Doctrine service, to get the EntityManager instance you need to call an additional ->getEntityManager();
$objectManager = $this->getServiceLocator()->get('doctrine')->getEntityManager();
This 'doctrine' service is typically available in Symfony framework and does not work in Zend Framework 2
If would suggest to consistently use one service name:
$objectManager = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
This should work.
Another solution
Another even better solution is to make your controller dependent on the ObjectManager, use the doctrine ObjectManagerAwareInterface inside the Controller class and inject the service in a factory.
To do this you need to register your controller in the module.config.php under controllers - factories like this:
'controllers' => array(
'factories' => array(
'OEC\Controller\BloodTypeController' => 'OEC\Controller\Factory\BloodTypeControllerFactory'
)
)
And then create a BloodTypeControllerFactory factory class like this:
<?php
namespace OEC\Controller\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use OEC\Controller\BloodTypeController;
class BloodTypeControllerFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return BloodTypeController
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$controllerPluginManager = $serviceLocator;
$serviceManager = $controllerPluginManager->get('ServiceManager');
$objectManager = $serviceManager->get('doctrine.entitymanager.orm_default');
return new BloodTypeController($objectManager);
}
}
And then add following to your OEC\Controller\BloodTypeController:
<?php
namespace OEC\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use DoctrineModule\Persistence\ObjectManagerAwareInterface;
use Doctrine\Common\Persistence\ObjectManager;
class BloodTypeController extends AbstractActionController implements ObjectManagerAwareInterface
{
/**
* #var ObjectManager
*/
protected $objectManager;
/**
* #param ObjectManager $objectManager
*/
public function __construct(ObjectManager $objectManager){
$this->objectManager = $objectManager
}
/**
* Set the object manager
*
* #param ObjectManager $objectManager
*/
public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Get the object manager
*
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager();
}
public function indexAction()
{
return new ViewModel();
}
public function listAll()
{
$objectManager = $this->getObjectManager();
//etc
}
}

How to unit test PHP traits

I want to know if there is a solution on how to unit-test a PHP trait.
I know we can test a class which is using the trait, but I was wondering if there are better approaches.
Thanks for any advice in advance :)
EDIT
One alternative is to use the Trait in the test class itself as I'm going to demonstrate bellow.
But I'm not that keen on this approach since there is no guaranty there are no similar method names between the trait, the class and also the PHPUnit_Framework_TestCase (in this example):
Here is an example trait:
trait IndexableTrait
{
/** #var int */
private $index;
/**
* #param $index
* #return $this
* #throw \InvalidArgumentException
*/
public function setIndex($index)
{
if (false === filter_var($index, FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException('$index must be integer.');
}
$this->index = $index;
return $this;
}
/**
* #return int|null
*/
public function getIndex()
{
return $this->index;
}
}
and its test:
class TheAboveTraitTest extends \PHPUnit_Framework_TestCase
{
use TheAboveTrait;
public function test_indexSetterAndGetter()
{
$this->setIndex(123);
$this->assertEquals(123, $this->getIndex());
}
public function test_indexIntValidation()
{
$this->setExpectedException(\Exception::class, '$index must be integer.');
$this->setIndex('bad index');
}
}
You can test a Trait using a similar to testing an Abstract Class' concrete methods.
PHPUnit has a method getMockForTrait which will return an object that uses the trait. Then you can test the traits functions.
Here is the example from the documentation:
<?php
trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class TraitClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');
$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->concreteMethod());
}
}
?>
You can also use getObjectForTrait , then assert the actual result if you want.
class YourTraitTest extends TestCase
{
public function testGetQueueConfigFactoryWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$config = $obj->getQueueConfigFactory();
$this->assertInstanceOf(QueueConfigFactory::class, $config);
}
public function testGetQueueServiceWithoutInstanceWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$service = $obj->getQueueService();
$this->assertInstanceOf(QueueService::class, $service);
}
}
Since PHP 7 we can now use annonymous classes...
$class = new class {
use TheTraitToTest;
};
// We now have everything available to test using $class

Categories