Symfony3 and PHP 7 return type decleration for Doctrine Repository methods - doctrine-orm

I really like PHP's return type declarations, and I want to use it on Symfony 3.
All controller methods should return a Response object, no problem there. But in Doctrine repositories though, Doctrine might return an Entity object, or null.
Consider this example:
You have created a simple Post entity.
You have created a custom findByName method in PostRepository:
PostRepository.php
public function findByName($name) : Post
{
$qb = $this->createQueryBuilder('p')
->where('p.name = :name')
->setParameter('name', $name);
$post = $qb->getQuery()->getOneOrNullResult();
return (null === $post) ? new Post() : $post;
}
You call this method from a controller, like this:
DefaultController.php
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:Post');
try {
$post = $repository->findByName('test');
} catch (\TypeError $e) {
$post = new Post();
}
return new Response(dump($post));
}
I am aware trying to catch exception won't execute because findByName() always returns a Post object.
My question is, where should we handle the exception? According to this answer, it is better to throw an exception. Should we ensure repository method does not throw an exception at all by using this:
$post = $qb->getQuery()->getOneOrNullResult();
return (null === $post) ? new Post() : $post;
or let PHP throw a TypeError exception and let controller handle it?
Doctrine, throws exceptions for getOneOrNullResult() and getSingleResult() methods as described here.
If this scenario doesn't make sense because it's better to let your controller handle the exception and return a "not found" page because post doesn't exist like this:
return $this->createNotFoundException();
Scenario #2
Post exists in the database, and another repository method is called, getApprovedComments(), no comments are returned, we're expecting an ArrayCollection but we get an array. This will throw PHP's TypeError exception.
I think code is going to be full of try/catch blocks. Is it ok to handle this kind of exceptions at a higher level to have less try/catch blocks in code?
At a second thought this is not the best solution, because code should be flexible enough to catch every TypeError exception and take proper action on how to render the page.

Related

Laravel 5 Cannot Mock View

I'm trying to write a really basic test for one of my controllers
/**
* THIS IS MY CONTROLLER. $this->badge is a repository
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('badges.index')->with([
'badges' => $badges = $this->badge->all()
]);
}
I'm using repositories which return Eloquent collections. My basic test is as follows:
public function testItShowsAllBadges()
{
// Arrange
//DISABLE AUTH MIDDLEWARE ON THIS ROUTE
$this->withoutMiddleware();
// MOCK THE REPO
$this->badge->shouldReceive('all')->andReturn(new Illuminate\Support\Collection);
// Act
$response = $this->action('GET', 'BadgeController#index');
// Assert
$this->assertResponseOk();
$this->assertInstanceOf('Illuminate\Support\Collection', $response->original->getData()['badges']);
$this->assertViewHas('badges');
}
This test fails with a message 'trying to get property of non-object'. This is because I do Auth::user()->something in the view.
So I need to mock the view but I don't know how. Can someone advise?
Other SO answers do not seem to work and just result in Exceptions being thrown in the test about methods not existing on the Mock. I have tried for example:
View::shouldReceive('make')
->once()
->andReturn(\Mockery::self())
Adding this before I call the route results in a 500 error 'Method Mockery_1_Illuminate_View_Factory::with() does not exist on this mock object'. I tried adding in
->shouldReceive('with')
->once()
->andReturn(\Mockery::self());
However this results in an Exception stating that getData() does not exist on this Mock Object. Even removing that assertion, assertViewHas('badges') fails saying the response was not a view.
Also I haven't understood if View::shouldReceive... is part of Arrange or Assert phase of the test?My understanding it is part of the arrange and should go before the $this->action(....)

Using custom renderer in grails unit testing (and in general) on JSON content types

I am trying to get a custom JSON renderer for exceptions working in my REST api.
I was able to get a custom marshaller working that did most of what I needed, but I would like to have control over the context that I don't have access to in the marshaller. The grails documentation shows how to write a custom renderer, and I have one that I think should work, but I can't use it during unit testing my REST controller.
Grails docs: http://grails.org/doc/2.3.4/guide/webServices.html#customRenderers
Does anyone know how I would initialize this renderer to be used in my controller actions during unit testing?
The above documentation only says how to set it up in the resources.groovy file.
When I was using the marshaller, I was able to do:
def setup(){
//Set up the custom JSON marshallers
JSON.registerObjectMarshaller(new CusomMarshaller(), 1)
}
But I don't see an equivalent method for Renderers. Can anyone point me in the right direction?
Further details:
Here is my renderer:
class JSONExceptionRenderer extends AbstractRenderer<Exception>{
JSONExceptionRenderer(){
super(Exception, [MimeType.JSON, MimeType.HAL_JSON, MimeType.TEXT_JSON] as MimeType[])
}
#Override
void render(Exception object, RenderContext context) {
log.warn("RENDERING")
Exception exception = (Exception) object
//Default to internal error
Integer code = 500
//If it is a defined exception with a more appropriate error code, then set it
if(exception instanceof RestException){
code = (Integer) ((RestException) exception).getCode()
}else if(exception instanceof MissingResourceException){
code = 404
}
context.status = HttpStatus.valueOf(code)
//Write the JSON
Writer writer = context.getWriter()
Map content = ["code":code, "status":"error", "message":exception.message]
JsonBuilder builder = new JsonBuilder(content)
builder.writeTo(writer)
}
}
And this is the way I am trying to get it to work:
try{
log.info "Throwing exception"
throw new NullPointerException("Test Exception")
}catch(Exception ex){
render ex as JSON
}
Thanks!
If you are using spock, you can inject the bean directly in Specification.
#TestFor(MyController)
class MyControllerSpec extends spock.lang.Specification {
def myCustomRenderer //bean name used in resources.groovy
//continue with tests
}
If you are using junit tests, then you can use defineBeans as:
#TestFor(MyController)
class MyControllerTests {
void setup() {
defineBeans {
myCustomRenderer(com.example.MyCustomRenderer)
}
}
//continue with tests
}
You can refer this answer as well for use of defineBeans.
I believe this is what you just need to do to test the behavior of the renderer.
After much digging around in the source. I thought I would post this here for others.
The reason my custom renderer didn't work was you have to use the "respond" method on the controller:
respond object
That will check the class ControllersRestApi with a very large respond method to find your renderer in the rendererRegistry and use it. This is different than the object marshallers which use the render as notation.
In addition you need to also flush the writer, which wasn't in the orgininal documentaiton:
builder.writeTo(writer)
writer.flush()

Issue testing Laravel Controller with Mockery | trying to get property of non-object

I'm very new to testing controllers and I'm running into a problem with a method(). I believe I'm either missing something in my test or my Controller / Repository is designed incorrectly.
The application I'm writing is basically one of those secure "one time" tools. Where you create a note, the system provides you with a URL, once that url is retrieved the note is deleted. I actually have the application written but I am going back to write tests for practice (I know that's backwards).
My Controller:
use OneTimeNote\Repositories\NoteRepositoryInterface as Note;
class NoteController extends \Controller {
protected $note;
public function __construct(Note $note)
{
$this->note = $note;
}
public function getNote($url_id, $key)
{
$note = $this->note->find($url_id, $key);
if (!$note) {
return \Response::json(array('message' => 'Note not found'), 404);
}
$this->note->delete($note->id);
return \Response::json($note);
}
...
I've injected my Note interface in to my controller and all is well.
My Test
use \Mockery as M;
class OneTimeNoteTest extends TestCase {
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('OneTimeNote\Repositories\EloquentNoteRepository');
}
public function mock($class)
{
$mock = M::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testShouldReturnNoteObj()
{
// Should Return Note
$this->mock->shouldReceive('find')->once()->andReturn('test');
$note = $this->call('GET', '/note/1234567890abcdefg/1234567890abcdefg');
$this->assertEquals('test', $note->getContent());
}
}
...
The error I'm getting
1) OneTimeNoteTest::testShouldReturnNoteObj
ErrorException: Trying to get property of non-object
/Users/andrew/laravel/app/OneTimeNote/Controllers/NoteController.php:24
Line 24 is in reference to this line found in my controller:
$this->note->delete($note->id);
Basically my abstracted repository method delete() obviously can't find $note->id because it really doesn't exist in the testing environment. Should I create a Note within the test and try to actually deleting it? Or would that be something that should be a model test? As you can see I need help, thanks!
----- Update -----
I tried to stub the repository to return a Note object as Dave Marshall mentioned in his answer, however I'm now receiving another error.
1) OneTimeNoteTest::testShouldReturnNoteObj
BadMethodCallException: Method Mockery_0_OneTimeNote_Repositories_EloquentNoteRepository::delete() does not exist on this mock object
I do have a delete() method in my repository and I know it's working when I test my route in the browser.
public function delete($id)
{
Note::find($id)->delete();
}
You are stubbing the note repository to return a string, PHP is then trying to retrieve the id attribute of a string, hence the error.
You should stub the repository to return a Note object, something like:
$this->mock->shouldReceive('find')->once()->andReturn(new Note());
Building upon Dave's answer, I was able to figure out what my problem is. I wasn't mocking the delete() method. I didn't understand the need to mock each individual method in my controller that would be called.
I just added this line:
$mock->shouldReceive('delete')->once()->andReturnNull();
Since my delete method is just deleting the note after it is found, I went ahead and mocked it but set it to return null.

Symfony2 : Handling error on a single controller

I made a controller to provide some webservices in JSON and i would like to provide some errors informations when Symfony throw an exception ( Error 500 ) , how can i write such a thing ?
The main purpose of the webservice is to update informations in Symfony DB provided by the caller in POST values.
in my controller i return response in JSON and i would like to handle Symfony exception ( like when the values provided or not fitting the schema designed ) to return details informations about errors .
i thought about making a test of every values but it would be a long time to write and not e easy code to read or using a try / catch system , but i think Symfony already provide such a function .
What do you think ?
Thx :)
I think you should use an EventListener to catch errors and return the proper response.
You can place it inside your SomethingBundle/EventListener folder and also you need to define a service in order to be loaded by Symfony.
More info: Event Listener
I hope I helped you, if you think I might be wrong, let me know. Good luck!
EDIT
If you only want to catch the errors inside a specific controller (for example) a controller called Webservice inside your SomethingBundle, you must check it before doing anything:
public function onKernelException(GetResponseForExceptionEvent $event)
{
$request = $event->getRequest();
if($this->getBundle($request) == "Something" && $this->getController($request) == "Webservice")
{
// Do your magic
//...
}
}
private function getBundle(Request $request)
{
$pattern = "#([a-zA-Z]*)Bundle#";
$matches = array();
preg_match($pattern, $request->get('_controller'), $matches);
return (count($matches)) ? $matches[0] : null;
}
private function getController(Request $request)
{
$pattern = "#Controller\\\([a-zA-Z]*)Controller#";
$matches = array();
preg_match($pattern, $request->get('_controller'), $matches);
return (count($matches)) ? $matches[1] : null;
}
DANGER This code is not tested, is only an approach for you to build your own code. But, if I have something wrong on it, tell me. I'd like to keep my examples clean.
Use JsonResponse Symfony class in sandbox:
use Symfony\Component\HttpFoundation\JsonResponse;
$data = array(); // array of returned response, which encode to JSON
$data['error_message'] = 'Bad request or your other error...');
$response = new JsonResponse($data, 500); // 500 - response status
return $response;

Controller Unit Test fails with SQLSTATE[42000] error

I want to unit test a controller action and have some problem toio excecute it.
The Error i got is the following:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'questionExists'
at line 1
The Method:
questionExists
is defined inside the Question Model.
My test function looks like this:
public function testView() {
$result = $this->testAction('/questions/questions/view/1', array('return' => 'vars'));
}
The Controller action i want to test looks like this:
public function view($id = null) {
if (!$this->Question->questionExists($id, 'id_virtual')) {
throw new NotFoundException(__('Invalid question'));
}
$options = array('conditions' => array('Question.id_virtual' => $id));
$this->set('question', $this->Question->find('first', $options));
}
So this is very confusing to me.
Can anybody point me to the right direction ?
Your model class is not found, CakePHP creates an instance on the fly for that table but this is not more than the basic Model class. When the code is then called it tries to call that method on an instance that is not your Question model in your app. You'll have to figure out what that happens.