I am a newbie in PHP.
I created a Laravel project using *composer**.
My controller has two endpoint uploadFile and testpost:
public function uploadFile(Request $request) {
//there are more code about reading uploaded file here. Everything is OK here.
$request = Request::create('/api/testpost', 'POST',
[],[],[],[],'{"this is" : "my test content"}');
return Route::dispatch($request);
}
public function testpost(Request $request){
Log::info($request->all());
return response()->json(["title"=>"this is the test get method"]);
}
uploadFile is invoked by POST action from a form which carries an uploaded JSON file.
I want to call testpost inside of uploadFile method using Request::create(...) and Route::dispatch(...).
testpost is invoked however the body of request is not as expected. The log file shows me that $request->all() does not return the request body which I expect to be {"this is" : "my test content"}.
My log file:
[2019-02-23 12:16:47] local.INFO: array (
'_token' => 'JzQjclRD4WaTkezqLxlU48D1dM7S3X2X3hok3kr4',
'employee_file' =>
Illuminate\Http\UploadedFile::__set_state(array(
'test' => false,
'originalName' => 'test_input_file.txt',
'mimeType' => 'text/plain',
'error' => 0,
'hashName' => NULL,
)),
)
What wrong in my code? API invocation or request body retrieval?
I know that we can call testpost method directly instead of calling API. However, I ultimate purpose is to know how to call an internal API.
You don't need to use internal API calls for this.
If both methods are in the same class you can invoke directly with
$this->methodName($args);
and it will return the result directly to the calling function. (provided you have a return statement in the method you are invoking)
Related
I have this method in my controller where I'm calling an external URL that returns a PDF file:
public function get()
{
$response = Http::withHeaders(['Content-Type' => 'application/pdf'])
->get('https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf')
->body();
return $response;
}
routes/api.php:
Route::get('/file', [FileController::class, 'get']);
Calling that route in the browser displays this gibberish output instead of the actual file:
If I do return response()->file($file), it's throwing an error:
Symfony \ Component\ HttpFoundation\ File \ Exception\ FileNotFoundException
Is there any way to achieve it without having to store the file first?
To send a file response without storing the file locally you an use streamDownload:
return response()->streamDownload(function () {
echo Http::withHeaders(['Content-Type' => 'application/pdf'])
->get('https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf')
->body();
}, 'c4611_sample_explain.pdf');
For the automated testing of a laravel API I am using the 'laravel' action on github actions, the one made by github actions.
The tests keep failing telling me invalid JSON returned from route, expected response code 200 but got 500, cannot read property status on null and cannot find in json
I'm using laravel sanctum. Could it be a csrf-token problem?
My action yml: https://gist.github.com/I2EJeffrey/77df8faac1b0f86623e2e4449f98d858
My response function:
* success response method.
*
* #return \Illuminate\Http\Response
*/
public function sendResponse($result, $message, $code = 200)
{
$response = [
'success' => true,
'data' => $result, // result is most often one or 2 arrays
'message' => $message,
];
return response()->json($response, 200);
}
Example test:
public function testSuccessfullyCreateAccommodationType()
{
$this->login(); // Login function that lots of tests need.
$response = $this->postJson('/api/v5/accommodations/1/types', ['accommodation_name'=>$this->createName()]);
$response
->assertJsonFragment(['success' => true])
->assertJsonStructure(['success', 'data' =>
[], 'message']); // The array is filled with keys
}
EDIT: 2 errors that I got by using withoutExceptionHandling: https://gist.github.com/I2EJeffrey/da23bfbdf5fba155456bd799a34f6276
EDIT 2: I also get the following warning: TTY mode requires /dev/tty to be read/writable.
EDIT 3: The client model and the client seeder. Whenever I run the tests a mysql docker container starts that gets a db migrated and seeded into it:
https://gist.github.com/I2EJeffrey/39c779df217c9a75a7569f6fa3957d77
https://gist.github.com/I2EJeffrey/41cbbdba8025d38547059d3a6f4d4392
My problem was that the DB didn't get seeded due to not having added $this->call(ClientSeeder::class); to the DatabaseSeeder. Which caused the routes to return null and thus wrong json.
I'm writing a feature test for a simple photo upload to S3 using Laravel Medialibrary.
This is the test:
/** #test */
public function a_user_can_upload_a_photo()
{
$this->withoutExceptionHandling();
Storage::fake('s3');
$user = factory(User::class)->create();
$this->signIn($user);
$this->post( route('users.update.photo', [
$user,
'photo' => UploadedFile::fake()->image('photo.jpeg', 500, 500)->size(1000),
]));
$this->assertEquals(1, Media::all()->count());
Storage::disk('s3')->assertExists('photo.jpeg');
}
and this is the relevant part of the controller method handling the upload:
public function uploadPhoto(User $user, Request $request)
{
// ...
try {
$user->addMediaFromRequest('photo')
->preservingOriginal()
->toMediaCollection('user-photo');
} catch (\Exception $e) {
dd($e);
Log::debug('User photo upload: ' . $e);
}
return redirect(route('users.edit.profile', $user));
}
The error catched there with dd($e) is
The current request does not have a file in a key named photo
I actually can test out that the error message is true with putting right in front of the try-catch:
dd($request->has('photo')); === true
but
dd($request->hasFile('photo')); === false
So I do not attach my mock file properly as a file to the request obviously, but I can't figure out how to do that and every example around my online searches suggest doing the test the way I already do.
Any help much appreciated.
The post() method expects the url as the first argument, and the data as the second argument.
You are using the helper route() to generate the url, which expects the user you want to edit as a parameter, but you are also passing the file data as a parameter to the helper route(), instead of passing it as an argument to the post() method:
$this->post( // just one argument for the post method
route('users.update.photo', [ // and two parameters for the route helper
$user,
'photo' => UploadedFile::fake()->image('photo.jpeg', 500, 500)->size(1000),
])
);
So use this syntax to pass the file as second argument for the post() method:
$this->post(
route('users.update.photo', ['user' => $user]), // first argument
[ 'photo' => UploadedFile::fake()->image('photo.jpeg', 500, 500)->size(1000) ] // second argument
);
I would like use a method of controller from another bundle, in my controller.
The method this->forward need a Response object, and i don't know how to use it.
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
// ... further modify the response or return it directly
return $response;
}
And i saw that i can use service but i want to know if its the best solution or they are another.
$this->forward takes arguments in this order:
Logical Name of controller action in string format i.e. 'AcmeHelloBundle:Hello:fancy'
Parameters to be passed as request variables in array format i.e. array(
'name' => $name,
'color' => 'green',
)
These parameters can be accessed in the controller using request access functions.
Sometimes you want to bypass security completely and run a command in another controller despite a user's permissions level. Luckily you can do that fairly easily.
First, run a use Command for your controller at the top of the controller you want to use the data from:
use AppBundle\Controller\MySourceDataController;
Then call that function from within your destination controller:
$response = MySourceDataController::getTheData( $option1, $option2 );
If you need to pass a Request object, you can do it this way:
$response = MySourceDataController::getTheData( new Request( array(
'server' => 'USAServer1',
) ), $option2 );
This returns a Request with the set parameter of server. I also defined a $option2, this would be a variable often defined in the URL such as:
* #Route("/mydata/{server}/", name="api-source-data")
* #param Request $request
* #param $server
Lastly, if you're passing JSON in that controller and want to convert it back to an object, you can run this bit of code on the $response:
if ( 0 === strpos( $response->headers->get( 'Content-Type' ), 'application/json' ) ) {
$response = json_decode( $response->getContent(), true );
}
Voila. Access any controller form any other controller and bypass security notation for the source controller. :)
I am dispatching some POST data to an action of a controller. That action echoes some json-encoded string. I want to verify that the json-encoded string of that action is as I want it. I want to know how I can get that string?
My test looks like this:
$this->request->setMethod('POST')
->setPost(['test' => 'databaseschema_Database']);
$params = ['action' => 'analysis', 'controller' => 'Index', 'module' => 'default'];
$urlParams = $this->urlizeOptions($params);
$url = $this->url($urlParams);
$result = $this->dispatch($url);
$this->assertJsonStringEqualsJsonString(
$result, json_encode(["status" => "Success"])
);
My test is failing and I am getting following message:
1) IndexControllerTest::testAnalysisAction
Expected value JSON decode error - Unknown error
stdClass Object (...) does not match expected type "NULL".
Can any one guide me on how to do this?
If you want to do unit testing, what you really want to do is extract the json encoding into it's own class (or a method inside a utils class or something) and then test those method instead of your whole controller.
The problem with your approach is that when running phpunit, there is not $_POST array. The code above does not show what is happening, but I guess there is different behaviour when run through apache and cli which causes your test to fail.
I would create a TransformerClass and test this in isolation:
class JsonTransformer
{
public function transformPostData(array $postArray)
{
// transformation happening
}
}
class JsonTransformerTest extends \PHPUnit_Framework_TestCase
{
public function testTransformPostData()
{
$transformer = new JsonTransformer();
$data = array('action' => 'analysis', 'controller' => 'Index', 'module' => 'default');
$result = $transformer->transformPostData(data);
$this->assertJsonStringEqualsJsonString($result, json_encode(array("status" => "Success")));
}
}
If you need to test your whole request/response, you would use some kind of HTTPClient, request the url, send the post data and see if the response is what you'd expect.
Everything in between (like faking the post data) leaves you with more problems and more code to maintain than it does you good.