I worte some API methods with the fractal package to output content. When I call the specific resources, everything returns empty.
To check if everything is working I performed some prints of the content variables inbetween. For example, if I take the $incidents variable in the index function I get returned all entries in the database as expected.
The same is true, when I call the $collection variable in the respondWithCollection method in the API controller. The data is available here as well. But the browser output is only this:
{
"data": {
"headers": {}
}
}
To keep it simple, this is the method to show all results of a database:
class ApiIncidentsController extends ApiController {
protected $incidentTransformer;
protected $fractal;
function __construct(IncidentTransformer $incidentTransformer){
$this->incidentTransformer = $incidentTransformer;
$this->beforeFilter('auth.basic', ['on' => 'post']);
$this->fractal = new Manager();
parent::__construct($this->fractal);
}
public function index()
{
$incidents = Incident::all();
if( ! $incidents) {
return Response::json([
'error' => [
'message' => 'There are no incidents in the database.',
'code' => 100
]
], 404);
} else {
return $this->respond([
'data' => $this->respondWithCollection($incidents, new IncidentTransformer),
]);
}
}
The API controller managing these calls is this:
class ApiController extends Controller {
protected $statusCode = 200;
protected $fractal;
public function __construct(Manager $fractal) {
$this->fractal = $fractal;
}
public function getStatusCode() {
return $this->statusCode;
}
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
public function respond($data, $headers = []) {
return Response::json($data, $this->getStatusCode(), $headers);
}
protected function respondWithItem($item, $callback) {
$resource = new Item($item, $callback);
$rootScope = $this->fractal->createData($resource);
return $this->respondWithArray($rootScope->toArray());
}
protected function respondWithArray(array $array, array $headers = []) {
return Response::json($array, $this->statusCode, $headers);
}
protected function respondWithCollection($collection, $callback) {
$resource = new Collection($collection, $callback);
$rootScope = $this->fractal->createData($resource);
return $this->respondWithArray($rootScope->toArray());
}
Update 1
This is the IncidentTransformer:
use League\Fractal\TransformerAbstract;
class IncidentTransformer extends TransformerAbstract {
public function transform(Incident $incident) {
return [
'incidentReference' => $incident['incidentReference'],
'latitude' => $incident['latitude'],
'longitude' => $incident['longitude'],
'archived' => (boolean) $incident['incidentArchived']
];
}
}
Update 2
I tried something else, by removing the respond wrapper. Then everything is fine. But I want to use the respond function I wrote to abstract the code. This seems to be the issue. When I pass in the data into the function, nothing is being returned. When I dump the variable data, There is a JSON Response returned. But the respondWithCollection Method within returns an array. I don't see why this is happening. Could this be the issue?
I adapted the method like this:
public function index()
{
$incidents = Incident::all();
if( ! $incidents) {
return Response::json([
'error' => [
'message' => 'There are no incidents in the database.',
'code' => 100
]
], 404);
} else {
$data = $this->respondWithCollection($incidents, new IncidentTransformer);
return $this->respond([
'data' => $data
]);
}
}
But still the output is empty. So it must be something with the response function.
You are returning Response::json() twice.
$this->respond returns Response::json, but also $this->respondWithCollection() returns respondWithArray() which also does.
Try something like:
public function index()
{
$incidents = Incident::all();
if( ! $incidents) {
return Response::json([
'error' => [
'message' => 'There are no incidents in the database.',
'code' => 100
]
], 404);
} else {
// getCollection instead of respondWithCollection
$data = $this->getCollection($incidents, new IncidentTransformer);
return $this->respond([
'data' => $data
]);
}
}
-
// getCollection instead of respondWithCollection
protected function getCollection($collection, $callback) {
$resource = new Collection($collection, $callback);
$rootScope = $this->fractal->createData($resource);
// don't respond again
//return $this->respondWithArray($rootScope->toArray());
return $rootScope->toArray();
}
Related
I have a class social which has:
protected $id;
public function __construct($request, $id)
{
Log::info('Processing...', ['request' => $request, 'id' => $id]);
try {
$client = new Client();
$url = sprintf($request);
$response = $client->get($url);
$json = json_decode((string) $response->getBody(), true);
return $json;
} catch (ClientException $exception) {
$responseBody = $exception->getResponse()->getBody(true);
Log::error($responseBody, ['entity_id' => $id]);
}
}
public function wikipedia($wikipedia_url, $id)
{
dd($json);
try {
$wikipedia_array = $json['parse']['text'];
$wikipedia_array = array_slice($wikipedia_array, 0, 9);
$wikipedia_array = implode($wikipedia_array, ',');
Log::info('Processed Wikipedia for', ['entity_id' => $id]);
return $wikipedia_array;
} catch (Exception $e) {
Log::error('Wikipedia:', ['message' => $e->getMessage(), 'entity_id' => $id]);
}
}
In another function I am calling a facade like this:
$id = $entity->id;
$wikipedia_id = $entity->wikipedia;
if (!empty($wikipedia_id)) {
$wikipedia_url = 'http://en.wikipedia.org/w/api.php?action=parse&prop=text§ion=0&disablelimitreport=1&format=json&page='.$wikipedia_id;
$wikipedia_html = Social::wikipedia($wikipedia_url, $id);
Log::info('Wikipedia ok for', ['entity_id' => $id]);
}
However I get this:
Type error: Too few arguments to function App\Helpers\Social::__construct(), 0 passed in /home/vagrant/liveandnow/app/Providers/SocialServiceProvider.php on line 35 and exactly 2 expected
Can anyone explain to me how to call a method, pass parameters to it but also pass them along to construct?
Here's my facade:
<?php
namespace App\Facade;
use Illuminate\Support\Facades\Facade;
class Social extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'social';
}
}
and service provider:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
the error lies in the service provider.
You define a constructor with 2 parameters,
public function __construct($request, $id)
but in your service provider you call it like this:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
You need to add both arguments when instantiating the Social class, for example like
return new Social("http://xyz.de", 1);
Hope this helps.
We have created an API that connects to the SOAP API system of the customer. We have a custom exception CustomerNotFound :
<?php
namespace App\Exceptions;
class CustomerNotFoundException extends \Exception
{
protected $code = 'customer_not_found';
protected $message = 'Customer not found!';
}
A Soap Class function for retrieving user information
public function getCustomerById($customerID)
{
$parameters = $this->appendToParameters([
'customerID' => $customerID])
->getParameters();
$result = $this->client->GetCustomerByID($parameters);
if ($result->SDKResult->ResultCode === 'Success') {
$data = $result->GetCustomerByIDResult;
return $data;
}
throw new CustomerNotFoundException();
}
And a controller that calls the soap class function:
public function getCustomerDetails(Request $request, $customerId)
{
try {
$customerDetails = $this->customerService->getCustomerById($customerId);
return $this->respond($customerDetails);
} catch (\SoapFault $e) {
$this->respondInternalError($e->getMessage());
} catch (\App\Exceptions\CustomerNotFoundException $c) {
$this->respondNotFound($c->getMessage());
}
}
I know the exception is working because when the soap function is also called on another controller, it works and then catches the exception properly when an invalid customer id is given. Also when I remove the try catch block and call the controller function with and invalid id, the api returns the CustomerNotFound exception. However, when it is inside a try catch block it just returns a STATUS 200 and an empty response (no jJSON, etc.). No errors are shown. Any ideas?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Response;
use JWTAuth;
class ApiController extends Controller
{
protected $statusCode = 200;
public function isDevMode(){
return request()->get('__devmode__', null) == 'ph';
}
public function err($msg, $code = 'error'){
return $this->respond([
'data'=> null,
'status'=> [
'code'=> $code,
'msg'=> $msg,
'success'=> false
]
]);
}
public function ok($data =[],$msg='Success',$code='success'){
return $this->respond([
'data'=> $data,
'status'=> [
'code'=> $code,
'msg'=> $msg,
'success'=> true
]
]);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
return $this;
}
public function respondNotFound($message = 'Not Found.',$code='not_found')
{
return $this->setStatusCode(404)->respondWithError($message,$code);
}
public function respondInternalError($message = 'Internal Server Error.', $code='internal_server_error')
{
return $this->setStatusCode(500)->respondWithError($message,$code);
}
public function respond($data, $headers = ['Content-type'=> 'application/json; charset=utf-8'])
{
return Response::json($data, $this->getStatusCode(), $headers);
}
public function respondWithError($message, $code = 'error')
{
return $this->respond([
'error' => [
'code' => $code,
'message' => $message
// 'status_code' => $this->getStatusCode()
]
]);
}
public function getUserTokenDetails()
{
$token = JWTAuth::getToken()->get();
try {
if (! $user = JWTAuth::parseToken())
{
return response()->json(['user_not_found'], 404);
}
}
catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token has expired.','token_expired');
}
catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token is invalid','token_invalid');
}
catch (Tymon\JWTAuth\Exceptions\JWTException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token not found.','token_not_found');
}
$payload = JWTAuth::parseToken()->getPayload();
$user = $payload['customClaims'];
return $user['CUST_ID'];
}
}
I am using a repository pattern in my Laravel 4 project but come across something which I think I am doing incorrectly.
I am doing user validation, before saving a new user.
I have one method in my controller for this:
public function addNewUser() {
$validation = $this->userCreator->validateUser($input);
if ( $validation['success'] === false )
{
return Redirect::back()
->withErrors($validation['errors'])
->withInput($input);
}
return $this->userCreator->saveUser($input);
}
Then the validateUser method is:
public function validate($input) {
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email_address' => 'unique:users'
);
$messages = [
];
$validation = Validator::make($input, $rules, $messages);
if ($validation->fails())
{
$failed = $validation->messages();
$response = ['success' => false, 'errors' => $failed];
return $response;
}
$response = ['success' => true];
return $response;
}
This may be okay, but I dont like doing the if statement in my controller? I would rather be able to handle that in my validation class.
But to be able to redirect from the validation class, I need to return the method in the controller.
What if I then want to have 5 methods called, I cant return them all?
I would like to be able to simply call the methods in order, then in their respective class handle what I need to and if there is any errors redirect or deal with them. But if everything is okay, simply ignore it and move to the next function.
So example:
public function addNewUser()
{
$this->userCreator->validateUser($input);
$this->userCreator->formatInput($input);
$this->userCreator->sendEmails($input);
return $this->userCreator->saveUser($input);
}
If doing the if statement in the controller isn't as bad as I think then I can continue, but this seems incorrect?
For repository pattern, you can use this :-
setup your basemodel like this
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model{
protected static $rules=null;
protected $errors=null;
public function validateForCreation($data)
{
$validation=\Validator::make($data,static::$rules);
if($validation->fails())
{
$this->errors=$validation->messages();
return false;
}
return true;
}
/**
* #return errors
*/
public function getErrors() { return $this->errors; }
}
now in your repository, add these methods
protected $model;
protected $errors=null;
public function model(){ return $this->model; }
public function getErrors(){ return $this->errors; }
public function create($inputs)
{
if(!$this->model->validateForCreation($inputs))
{
$this->errors=$this->model->getErrors();
return false;
}
$new=$this->model->create($inputs);
return $new;
}
and the controller will look like this..
public function postCreate(Request $request)
{
$inputs=$request->all();
if($new=$this->repo->create($inputs))
{
return redirect()->back()
->with('flash_message','Created Successfully');
}
return redirect()->back()->withInput()->withErrors($this->repo->getErrors())
->with('flash_message','Whoops! there is some problem with your input.');
}
I'm trying to implement some unit tests for my REST controller. Everything works fine until I use the Validator facade. The index and show tests are working fine.
The error I'm getting is:
Fatal Error: Call to a member function setAttributeName() on a non-object in D:\....\controllers\AllergyController.
My code is:
//Unit test
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
public function testShow()
{
$this->allergy->shouldReceive('find')->once()->andReturn(array());
$this->call('GET', 'api/allergy/1');
$this->assertResponseOk();
}
public function testStore()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'foo');
$this->allergy->shouldReceive('create')->once();
$validator->shouldReceive('make')->once();
$validator->shouldReceive('setAttributeNames')->once();
$this->call('POST', 'api/allergy', $input);
$this->assertResponseOk();
}
}
My controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
public function index()
{
...
}
public function show($id)
{
...
}
public function store()
{
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.add.name')
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->create(Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
}
}
I can't seem to fgure out why it's throwing this error. When I call the controller with a normal api call it works fine.
Any help is much appreciated!
You are probably wanting to return the validator double from the stubbed make call.
$validator->shouldReceive('make')->once()->andReturn($validator);
Problem is that i can't test one function, because it is touching other functions of the same repository.
Do I need to test one function in isolation from other functions in same repository, or it is normal that one function can access other functions in same repository ?
If function needs to be tested in isolation from other, how it can be done, because I don't understand how I can mock repository in which I'm working. I understand how to mock dependencies, but how to mock other functions in same repository ?
Am I mocking model correctly in setUp method in the test?
Code:
Real world binding of and repository:
// Bind User repository interface
$app->bind('MyApp\Repositories\User\UserInterface', function () {
return new EloquentUser(new User);
});
EloquentUser.php:
public function __construct(Model $user)
{
$this->user = $user;
}
public function findById($id)
{
return $this->user->find($id);
}
public function replace($data)
{
$user = $this->findById($data['user']['id']);
// If user not exists, create new one with defined values.
if ( ! $user) {
return $this->create($data);
} else {
return $this->update($data);
}
}
public function create($data)
{
$user = $this->user->create($data['user']);
if ($user) {
return $this->createProfile($user, $data['profile']);
} else {
return false;
}
}
private function createProfile($user, $profile)
{
return $user->profile()->create($profile);
}
public function update($user, $data)
{
foreach ($data['user'] as $key => $value) {
$user->{$key} = $value;
}
if (isset($data['profile']) && count($data['profile']) > 0) {
foreach ($data['profile'] as $key => $value) {
$user->profile->$key = $value;
}
}
return ($user->push()) ? $user : false;
}
EloquentUserTest.php
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('Illuminate\Database\Eloquent\Model', 'MyApp\Models\User\User');
App::instance('MyApp\Models\User\User', $this->user);
$this->repository = new EloquentUser($this->user);
}
public function testReplaceCallsCreateMethod()
{
$data = [
'user' => [
'id' => 1,
'email' => 'test#test.com',
],
'profile' => [
'name' => 'John Doe',
'image' => 'abcdef.png',
],
];
// Mock the "find" call that is made in findById()
$this->user->shouldReceive('find')->once()->andReturn(false);
// Mock the "create" call that is made in create() method
$this->user->shouldReceive('create')->once()->andReturn(true);
// Run replace method that i want to test
$result = $this->repository->replace($data);
$this->assertInstanceOf('Illuminate\Database\Eloquent\Model', $result, 'Should be an instance of Illuminate\Database\Eloquent\Model');
}
When running this test I got:
Fatal error: Call to a member function profile() on a non-object in C:\Htdocs\at.univemba.com\uv2\app\logic\Univemba\Repositories\User\EloquentUser.php on line 107
So it means that Test is trying to touch function in EloquentUser.php:
private function createProfile($user, $profile)
{
return $user->profile()->create($profile);
}
Do I need to mock createProfile ? because profile() cant be found. And if I need to do this, how can i do it because this function is in same repository that i'm testing?
Question is solved.
Just needed to create one more Model instance and pass it in mocked method.
My Working setUp method:
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('MyApp\Models\User\User');
App::instance('MyApp\Models\User\User', $this->user);
$this->repository = new EloquentUser($this->user);
}
Working test method:
public function testReplaceCallsCreateMethod()
{
$data = [
'user' => [
'id' => 1,
'email' => 'test#test.com',
'password' => 'plain',
],
'profile' => [
'name' => 'John Doe',
'image' => 'abcdef.png',
],
];
// Mock Model's find method
$this->user->shouldReceive('find')->once()->andReturn(false);
// Create new Model instance
$mockedUser = Mockery::mock('MyApp\Models\User\User');
// Mock Models profile->create and pass Model as a result of a function
$mockedUser->shouldReceive('profile->create')->with($data['profile'])->andReturn($mockedUser);
// Pass second instance Model as a result
$this->user->shouldReceive('create')->once()->andReturn($mockedUser);
// Now all $user->profile is properly mocked and will return correct data
$result = $this->repository->replace($data);
$this->assertInstanceOf('Illuminate\Database\Eloquent\Model', $result, 'Should be an instance of Illuminate\Database\Eloquent\Model');
}