PHP with Symfony framework:
First of all before the context:
My input form is being built by form builder. Nothing is wrong there. So that is not the problem
I am making a sms validator system. I have a controller, and 2 services(validatorservice, smsapi(for api call)) Now my validatorservice looks like this:
class ValidatorService
{
public function validate($telefoonnummer)
{
$pregpatternNL = '(^\+[0-9]{2}|^\+[0-9]{2}\(0\)|^\(\+[0-9]{2}\)\(0\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\-\s]{10}$)';
if (!preg_match($pregpatternNL, $telefoonnummer)) {
return false;
} else {
return true;
}
}
}
Then my homecontroller:
use App\Service\ValidatorService;
class HomeController extends AbstractController
{
/** #var SmsApi */
private $smsApi;
/** #var validatorService */
private $validatorService;
public function __construct1(SmsApi $smsApi, Validatorservice
$validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
/**
* #Route("/")
* #Template()
*
* #param Request $request
*
* #return array
*/
public function indexAction(Request $request)
{
$form = $this->createForm(
SmsLogFormType::class,
new SmsLog(),
[
'method' => 'post',
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var SmsLog $smslog */
$formData = $form->getData();
try {
$telefoonnummer = $formData->getTel();
$telefoonnummer = preg_replace('/[^0-9]/', '', $telefoonnummer);
$validatorservices = $this->validatorService-
>validate($telefoonnummer);
if ($validatorserviceres === false) {
$this->addFlash(
'notice',
'telefoonnummer onjuist formaat'
);
exit;
} else {
blablabla
}
Now whatever i try i get the error :
Call to a member function validate() on null
At first i thought maybe its something with the constructor names, but found online that that doesn't matter at all (also i didn't receive any code errors there)
Then i tried adding echo's to the if statement in my service. Maybe return true or false is seen as null but this doesn't work either.
I guess it's because of the number of arguments per constructor. If you define multiple constructors for a class, they should have different argument counts.
What you could do instead is to check whether or not the object you received is part of the wanted class/classes.
Or create static functions that instatiate the class with different object types.
EDIT
Use the default autowiring mechanisms:
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
It should work as intended if you change your Code to this :
/** #var SmsApi */
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->validatorService = $validatorService;
$this->smsApi = $smsApi;
}
__construct1 and __construct2 are not native functions of php, so when the class is loaded, the constructors are not invoking and validatorService/smsApi are not being set (so they are null). The native function is called __construct.
/** #var SmsApi */
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
Or if doest not work, inject the services as arg in
public function indexAction(Request $request)
so...
public function indexAction(Request $request,SmsApi $smsApi, ValidatorService $validatorService)
and use $validatorService->validate();
Related
I'm having trouble getting the value passed via parent__construct in a Controller's child class and using that value in a class called View.
When performing a var_dump in the __Construct of the Controller class I can retrieve the value sent from the Form class, but when I try to retrieve this same value in a METHOD inside the View class, it appears as null.
Could you help me with this problem?
Here are the codes:
============================================
class Form extends Controller
{
//General builder of the WEB pages
public function __construct()
{
//Redirecting the entire site for maintenance
//redirect("/ops/maintenance");
parent::__construct(__DIR__ . "/../../../themes/" . THEME . "/pages/");
}
}
============================================
class controller
{
/** #var View */
protected $view;
/** #var Message */
protected $message;
//Builder extending classes
/**
* Controller constructor
* #param string|null $pathToViews
*/
public function __construct(string $pathToViews = null)
{
$this->view = new View($pathToViews);
//the value passed in parent::__construct() appears here
var_dump($pathToViews);
$this->message = new Message();
}
}
============================================
//Class responsible for handling Views requests
class view
{
protected $pathToViews;
public function __construct(string $pathToViews = null)
{
$this->pathToViews = $pathToViews;
}
//Function to Load the View and send content from the backend to the frontend
public function show($viewName, $data = [])
{
var_dump($this->pathToViews);
}
}
Update:
If you don't want to call the show method in the controller class, but you want to instantiate that class in your controller class you should find a way to get the view class accessible outside of your controller class. Just instantiating view class again outside of the controller is another instance and will indeed show a null value on the $pathToViews variable.
One way of doing this is like this:
class controller
{
/** #var View */
protected $view;
//Builder extending classes
/**
* Controller constructor
* #param string|null $pathToViews
*/
public function __construct(string $pathToViews = null)
{
$this->view = new View($pathToViews);
var_dump($pathToViews);
}
public function getView() : View
{
return $this->view;
}
}
//Class responsible for handling Views requests
class View
{
protected $pathToViews;
public function __construct(string $pathToViews = null)
{
$this->pathToViews = $pathToViews;
echo $this->pathToViews;
}
//Function to Load the View and send content from the backend to the frontend
public function show($viewName, $data = [])
{
var_dump($this->pathToViews);
}
}
$controller = new controller('testString');
$view = $controller->getView();
$view->show('test');
Another way of doing this is using the principle of dependency inversion
class controller
{
/** #var View */
protected $view;
//Builder extending classes
/**
* Controller constructor
* #param View $view
* #param string|null $pathToViews
*/
public function __construct(View $view, string $pathToViews = null)
{
$this->view = $view;
$this->view->setPathtoViews($pathToViews);
var_dump($pathToViews);
}
public function getView() : View
{
return $this->view;
}
}
//Class responsible for handling Views requests
class View
{
protected $pathToViews;
public function setPathtoViews(string $pathToViews)
{
$this->pathToViews = $pathToViews;
}
//Function to Load the View and send content from the backend to the frontend
public function show($viewName, $data = [])
{
var_dump($this->pathToViews);
}
}
$view = new View();
$controller = new controller($view, 'testString');
$view->show('test');
?>
Original answer
Your code runs fine on my machine. I have run the code block below. What I did change to make it work was actually calling $this->view->show(). In your code example that wasn't the case.
As a small note. I know php is case insensitive but as a good practice call your classes with the same casing as how you defined them (view vs View).
<?php
class controller
{
/** #var View */
protected $view;
//Builder extending classes
/**
* Controller constructor
* #param string|null $pathToViews
*/
public function __construct(string $pathToViews = null)
{
$this->view = new View($pathToViews);
$this->view->show('test');
var_dump($pathToViews);
}
}
//Class responsible for handling Views requests
class View
{
protected $pathToViews;
public function __construct(string $pathToViews = null)
{
$this->pathToViews = $pathToViews;
echo $this->pathToViews;
}
//Function to Load the View and send content from the backend to the frontend
public function show($viewName, $data = [])
{
var_dump($this->pathToViews);
}
}
$controller = new controller('testString');
?>
I'm currently migrating a ZF2 application to ZF3.
Mostly everything is going smoothly but I'm stuck on one thing.
In my Module.php, I have an ACL management using zend-permissions-acl.
class Module
{
protected $defaultLang = 'fr';
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
if (!$e->getRequest() instanceof ConsoleRequest){
$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError'));
$eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'));
$eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
$this->initAcl($e);
$eventManager->attach('route', array($this, 'checkAcl'));
}
}
public function checkAcl(MvcEvent $e) {
$app = $e->getApplication();
$sm = $app->getServiceManager();
$route = $e -> getRouteMatch() -> getMatchedRouteName();
$authService = $sm->get('AuthenticationService');
$jwtService = $sm->get('JwtService');
$translator = $sm->get('translator');
$identity = null;
try {
$identity = $jwtService->getIdentity($e->getRequest());
} catch(\Firebase\JWT\ExpiredException $exception) {
$response = $e->getResponse();
$response->setStatusCode(401);
return $response;
}
if(is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation
$authService->setJwtMode(false);
$identity = $authService->getIdentity();
}
$userRole = 'default';
$translator->setLocale($this->defaultLang);
if(!is_null($identity))
{
$userRole = $identity->getType();
//check if client or prospect
if($userRole >= User::TYPE_CLIENT)
{
$userManagementRight = UserRight::CREATE_USERS;
if($identity->hasRight($userManagementRight))
$userRole = 'userManagement';
}
$translator->setLocale($identity->getLang());
}
if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) {
$response = $e -> getResponse();
$response->setStatusCode(403);
return $response;
}
public function initAcl(MvcEvent $e) {
//here is list of routes allowed
}
}
My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
Basically, I just need to inject dependencies into Module.php.
I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.
Any feedback on this would be greatly appreciated.
Regards,
Robert
To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)
You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )
In your module.config.php:
'listeners' => [
// Listing class here will automatically have them "activated" as listeners
ActiveSessionListener::class,
],
'service_manager' => [
'factories' => [
// The class (might need a) Factory
ActiveSessionListener::class => ActiveSessionListenerFactory::class,
],
],
The Factory
<?php
namespace User\Factory\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use User\Listener\ActiveSessionListener;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
class ActiveSessionListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $entityManager */
$entityManager = $container->get(EntityManager::class);
/** #var AuthenticationService $authenticationService */
$authenticationService = $container->get(AuthenticationService::class);
return new ActiveSessionListener($authenticationService, $entityManager);
}
}
The Listener
<?php
namespace User\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\AuthenticationService;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Mvc\MvcEvent;
/**
* Class ActiveSessionListener
*
* #package User\Listener
*
* Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager.
* A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it.
*/
class ActiveSessionListener implements ListenerAggregateInterface
{
/**
* #var AuthenticationService
*/
protected $authenticationService;
/**
* #var ObjectManager|EntityManager
*/
protected $objectManager;
/**
* #var array
*/
protected $listeners = [];
/**
* CreatedByUserListener constructor.
*
* #param AuthenticationService $authenticationService
* #param ObjectManager $objectManager
*/
public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager)
{
$this->setAuthenticationService($authenticationService);
$this->setObjectManager($objectManager);
}
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000);
}
/**
* #param Event $event
*
* #throws \Doctrine\Common\Persistence\Mapping\MappingException
* #throws \Doctrine\ORM\ORMException
*/
public function haveDoctrineManagerUser(Event $event)
{
if ($this->getAuthenticationService()->hasIdentity()) {
// Get current unmanaged (by Doctrine) session User
$identity = $this->getAuthenticationService()->getIdentity();
// Merge back into a managed state
$this->getObjectManager()->merge($identity);
$this->getObjectManager()->clear();
// Get the now managed Entity & replace the unmanaged session User by the managed User
$this->getAuthenticationService()->getStorage()->write(
$this->getObjectManager()->find(User::class, $identity->getId())
);
}
}
/**
* #return AuthenticationService
*/
public function getAuthenticationService() : AuthenticationService
{
return $this->authenticationService;
}
/**
* #param AuthenticationService $authenticationService
*
* #return ActiveSessionListener
*/
public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener
{
$this->authenticationService = $authenticationService;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*
* #return ActiveSessionListener
*/
public function setObjectManager($objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
}
The important bits:
The Listener class must implement ListenerAggregateInterface
Must be activated in the listeners key of the module configuration
That's it really. You then have the basic building blocks for a Listener.
Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.
NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']);
}
public function callbackFunction (MvcEvent $event) {...}
I need to access a $_POST variable from a service and I don't want to pass request as a paramter as I think this forces me to use scope: request in the service and I have bad memories from this scope from the past, as this forces a service instance per request, instead of one instance per application.
The thing is that $_POST works all right, but as symfony best practices recommends to avoid using php primitives I ask if there's a better way to do it (avoiding scope: request in service)
If you are using 2.4+ you can use the request_stack, see here.
You would use it like..
services.yml
your.service:
class: FQCN\To\Your\Service
arguments:
- #request_stack
FQCN\To\Your\Service
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class Service
{
/**
* #var RequestStack
*/
private $requestStack;
/**
* #var Request
*/
private $request;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function yourCall()
{
$param = $this->getRequest()->request->get('your-post-parameter');
//...
}
/**
* Get current request object
*
* #return Request
*/
private function getRequest()
{
if (null === $this->request) {
$this->request = $this->requestStack->getCurrentRequest();
}
return $this->request;
}
}
I don't see any problem with doing it like below which is what I did some time ago. You're not injecting whole Request.
CONTROLLER
public function searchByGetAction(Request $request)
{
//.......
$results = $this->productSearchService->findWithSimpleArray(
$request->query
);
//.......
}
public function searchByPostAction(Request $request)
{
//.......
$results = $this->productSearchService->findkWithNestedArray(
json_decode($request->getContent(), true)
);
//.......
}
SERVICE
public function findWithSimpleArray(ParameterBag $searchParameters)
{
$name = $searchParameters->get('name');
$surname = $searchParameters->get('surname');
//.......
}
public function findWithNestedArray($searchParameters = [])
{
$name = isset($searchParameters['name']) ? $searchParameters['name'] : null;
$surname = isset($searchParameters['surname']) ? $searchParameters['surname'] : null;
//.......
}
I need to do a custom isGranted method (not using Rbac or acl module from community). So I have a service which provides the functionality. But this code:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new \Exception("you_are_not_allowed", 1);
}
...is duplicated in each controller and each action I have. Parameters are changing of course depends on the permission ('ZF_INV_HOM', 'ZF_TODO_DELETE' ...).
I think it's not a bad idea to do this code before the controller is called, but I can't figure what is the best solution (best architecture), and how to pass those parameters to it (I thought about annotation on controllers but how to handle this ?).
The point is, if I have to modify this code I can't imagine to do that hundreds of times, for each controllers, each action I have I need to have this code in one place.
If you don't want to pollute your Module with all this code you can also make a listener class and attach only the listener in your bootstrap method:
<?php
namespace Application\Listener;
use Application\Service\UserService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\SharedListenerAggregateInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* #var AuthenticationServiceInterface
*/
protected $authService;
/**
* #var UserService
*/
protected $userService;
/**
* #var \Zend\Stdlib\CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* #param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* #param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* #return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* #param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* #return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* #param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
You need to setup a factory to inject your dependencies:
<?php
namespace Application\Listener;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* #param ServiceLocatorInterface $serviceLocator
* #return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$userService = $serviceLocator->get('Application\Service\UserService');
return new IsAllowedListener($authService, $userService );
}
}
And register all this in config:
'service_manager' => array(
'factories' => array(
'Application\Listener\IsAllowedListener' => 'Application\Listener\IsAllowedListenerFactory'
)
)
And then in bootstrap:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application\Listener\IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
Instead of using AbstractActionController::class, you could also make a specific class, so you will only listen to instances of that class.
So for example AbstractIsAllowedActionController::class or something like that.
By attaching an event listener to the SharedEventManager you can target all controllers and have the authorization check in just one place.
In this case the target is Zend\Mvc\Controller\AbstractActionController which means any controller extending it will execute the listener. The high priority of this listener will mean that it is executed prior to the target controller action, giving you the chance to handle any requests that have not been authorized.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
\Zend\Mvc\Controller\AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
In each controller there would need to be some way that you can determine which 'resource' is being accessed.
As an example it could implement this interface
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
The listener could then look like this.
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule\Service\UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}
i'm trying to test a simple class. I'm following this tutorial( http://code.tutsplus.com/tutorials/testing-laravel-controllers--net-31456 ).
I have this error, while running tests:
Method Mockery_0_App_Interfaces_MealTypeRepositoryInterface::getValidator() does not exist on this mock object
Im using repository structure. So, my controller calls repository and that returns Eloquent's response.
I'm relatively new in php and laravel. And I've started learning to test a few days ago, so I'm sorry for that messy code.
My test case:
class MealTypeControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->mock = Mockery::mock('App\Interfaces\MealTypeRepositoryInterface');
$this->app->instance('App\Interfaces\MealTypeRepositoryInterface' , $this->mock);
}
public function tearDown()
{
Mockery::close();
}
public function testIndex()
{
$this->mock
->shouldReceive('all')
->once()
->andReturn(['mealTypes' => (object)['id' => 1 , 'name' => 'jidlo']]);
$this->call('GET' , 'mealType');
$this->assertViewHas('mealTypes');
}
public function testStoreFails()
{
$input = ['name' => 'x'];
$this->mock
->shouldReceive('getValidator')
->once()
->andReturn(Mockery::mock(['fails' => true]));
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->call('POST' , 'mealType' , $input ); // this line throws the error
$this->assertRedirectedToRoute('mealType.create');//->withErrors();
$this->assertSessionHasErrors('name');
}
}
My EloquentMealTypeRepository:
Nothing really interesting.
class EloquentMealTypeRepository implements MealTypeRepositoryInterface
{
public function all()
{
return MealType::all();
}
public function find($id)
{
return MealType::find($id);
}
public function create($input)
{
return MealType::create($input);
}
public function getValidator($input)
{
return MealType::getValidator($input);
}
}
My eloquent implementation:
Nothing really interresting,too.
class MealType extends Model
{
private $validator;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'meal_types';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [];
public function meals()
{
return $this->hasMany('Meal');
}
public static function getValidator($fields)
{
return Validator::make($fields, ['name' => 'required|min:3'] );
}
}
My MealTypeRepositoryInterface:
interface MealTypeRepositoryInterface
{
public function all();
public function find($id);
public function create($input);
public function getValidator($input);
}
And finally, My controller:
class MealTypeController extends Controller {
protected $mealType;
public function __construct(MealType $mealType)
{
$this->mealType = $mealType;
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$mealTypes = $this->mealType->all();
return View::make('mealTypes.index')->with('mealTypes' ,$mealTypes);
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
$mealType = new MealTypeEloquent;
$action = 'MealTypeController#store';
$method = 'POST';
return View::make('mealTypes.create_edit', compact('mealType' , 'action' , 'method') );
}
/**
* Validator does not work properly in tests.
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$input = ['name' => $request->input('name')];
$mealType = new $this->mealType;
$v = $mealType->getValidator($input);
if( $v->passes() )
{
$this->mealType->create($input);
return Redirect::to('mealType');
}
else
{
$this->errors = $v;
return Redirect::to('mealType/create')->withErrors($v);
}
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
return View::make('mealTypes.show' , ['mealType' => $this->mealType->find($id)]);
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
$mealType = $this->mealType->find($id);
$action = 'MealTypeController#update';
$method = 'PATCH';
return View::make('mealTypes.create_edit')->with(compact('mealType' , 'action' , 'method'));
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$mealType = $this->mealType->find($id);
$mealType->name = \Input::get('name');
$mealType->save();
return redirect('mealType');
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
$this->mealType->find($id)->delete();
return redirect('mealType');
}
}
That should be everything. It's worth to say that the application works, just tests are screwed up.
Does anybody know, why is that happening? I cant see a difference between methods of TestCase - testIndex and testStoreFails, why method "all" is found and "getValidator" is not.
I will be thankful for any tips of advices.
Perhaps an aside, but directly relevant to anyone finding this question by its title:
If:
You are getting the error BadMethodCallException: Method Mockery_0_MyClass::myMethod() does not exist on this mock object, and
none of your mocks are picking up any of your subject's methods, and
your classes are being autoloaded, (e.g. using composer)
then before making your mock object, you need to force the loading of that subject, by using this line of code:
spl_autoload_call('MyNamespace\MyClass');
Then you can mock it:
$mock = \Mockery::mock('MyNamespace\MyClass');
In my PHPUnit tests, I often put that first line into the setUpBeforeClass() static function, so it only gets called once and is isolated from tests being added/deleted. So the Test class looks like this:
class MyClassTest extends PHPUnit_Framework_TestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
spl_autoload_call('Jodes\MyClass');
}
public function testIt(){
$mock = \Mockery::mock('Jodes\MyClass');
}
}
I have forgotten about this three times now, each time spending an hour or two wondering what on earth the problem was!
I have found a source of this bug in controller.
calling wrong
$v = $mealType->getValidator($input);
instead of right
$v = $this->mealType->getValidator($input);