To test a method show()
as part of a controller in PHP Artisan Tinker you have to make sure the controller is loaded and or the modal in use if the case is as well. It can be rather daunting at first. Certainly if you are like me and just want to barge in, get callback data, variable data and more real data quick.
Show Method
The method in question I was working with and tinkering with in PHP Artisan Tinker is
public function show(Download $download)
{
$extension = pathinfo(storage_path("app/{$download->path}"), PATHINFO_EXTENSION);
return response()->download(storage_path("app/{$download->path}"), "$download->name.{$extension}", [
'Content-Type' => $download->mime,
]);
}
It is a method to show downloads added in a response. A little confusing as well as you would think it would perhaps return a view here, but for that we already use an index()
method. It however returns a response with file name, extension as well as mime type.
Dependency Injection
And as you can see it has the Download Modal injected using the Laravel Container for dependency injection as parameters:
public function show(Download $download)
And to work with that and not get errors like:
>>> $controller = app()->make('App\Http\Controllers\Admin\DownloadsController');
=> App\Http\Controllers\Admin\DownloadsController {#5043}
>>> app()->call([$controller, 'show'], ['id' => 1]);
Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException with message 'The file "/Users/jasper/code/domain.com/valet/storage/app/" does not exist'
you need to set things up properly.
App Method
The usage of app()
[laravel docs] to work with the Laravel Service Container is good and the way a call is made post storing a new controller instance in a variable too.
Make Methods
Make method make()
is used to resolve a new instance from the container. You can feed it the class or interface you need. See https://laravel.com/docs/master/container#the-make-method .
So we used app()->make()
to get a new controller instance and then we called upon it.
But that is where things get stuck. It cannot load a file and is not really getting database data that has been injected
Set Needed Classes
So this roadblock means you need to run the following in PHP Artisan Tinker REPEL to actually get some results from this method properly. First you need to set the Controller and modal you will be using. In our case DownloadsController and Download Modal
use App\Http\Controllers\Admin\DownloadsController;
use App\Models\Download;
Variable to load first table item
The next step now that we have preloaded these classes including the Download model class is to set a new modal instance to load the record you would like to work with
$download = Download::first();
We pick the first row and in our case only record we have. We use the double colon because we are accessing static properties of a class.
Static Types in PHP
The Scope Resolution Operator (also called Paamayim Nekudotayim) or in simpler terms, the double colon, is a token that allows access to static, constant, and overridden properties or methods of a class.
Declaring class properties or methods as static makes them accessible without needing an instantiation of the class. php docs
Laravel Collections Method First
With first()
(docs link) we return the first element in the collection. And Eloquent queries and so Laravel Models always return as collection instances so they can be worked with using collection methods such as first()
.
A property declared as static cannot be accessed with an instantiated class object (though a static method can). php docs
First Method Details
So that would mean that first()
is a static method. And digging deeper and finding it at vendor/laravel/framework/src/Illuminate/Collections/Arr.php
it is static:
/**
* Return the first element in an array passing a given truth test.
*
* @param iterable $array
* @param callable|null $callback
* @param mixed $default
* @return mixed
*/
public static function first($array, callable $callback = null, $default = null)
{
if (is_null($callback)) {
if (empty($array)) {
return value($default);
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return value($default);
}
Note 1 Eloquent is Laravel’s Database ORM (Object Relational Mapper) where Eloquent models are the query builders
Note 2 The results of Eloquent queries are always returned as Collection
instances. The Eloquent Collection
class extends Laravel’s base Illuminate\Support\Collection
class, which provides a variety of helpful methods for interacting with data collections. For example, the reject
method may be used to remove models from a collection based on the results of an invoked closure
Note 3 Since all of Laravel’s collections implement PHP’s iterable interfaces, you may loop over collections as if they were an array. See PHP Doc and this SO thread:
Laravel’s Collection
class implements the following interfaces:
ArrayAccess, ArrayableInterface, Countable, IteratorAggregate, JsonableInterface
Note 4 Laravel collections provide a variety of extremely powerful methods for mapping and reducing data. For more information on Laravel collections, check out the collection documentation.
This method first()
based on collection array is loaded as a new collection in vendor/laravel/framework/src/Illuminate/Collections/Collection.php
using:
public function first(callable $callback = null, $default = null) { return Arr::first($this->items, $callback, $default); }
And because it is loaded there it can be used on each Eloquent query being a new collection class instance.
New Controller Instance
And then call a new controller instance
$controller = app()->make('App\Http\Controllers\Admin\DownloadsController');
This will then allow you to get feedback on a show method in use in our case:
>>> use App\Http\Controllers\Admin\DownloadsController;
>>> use App\Models\Download;
>>> $download = Download::first();
=> App\Models\Download {#5093
id: 1,
path: "downloads/HmBTtPJgeV0f2LNCjXW8ZeBkP3xvgI6DCIt8i5tp.pdf",
name: "Grids",
mime: "application/pdf",
size: 37480.0,
active: 1,
total_downloads: 0,
created_at: "2020-07-13 04:51:22",
updated_at: "2020-07-13 04:51:22",
}
>>> $controller = app()->make('App\Http\Controllers\Admin\DownloadsController');
=> App\Http\Controllers\Admin\DownloadsController {#5105}
>>> app()->call([$controller, 'show'], ['download' => $download])
=> Symfony\Component\HttpFoundation\BinaryFileResponse {#5136
+headers: Symfony\Component\HttpFoundation\ResponseHeaderBag {#487},
}
Props Devin Gray at Laracasts for getting this far and explaining things!
So to have this line
app()->call([$controller, 'show'], ['download' => $download])
to work you need…. to feed REPEL with more data on the modal object as well as the controller.
Only then you have a binary file response and a Response Headerbag. What do those mean then?
Symfony Binary File Response & Headerbag
So what is the Binary File Response? Well for that you can check the code or even better the Symfony documentation. Here some elements on it that stood out:
The BinaryFileResponse
will automatically handle Range
and If-Range
headers from the request. It also supports X-Sendfile
(see for nginx and Apache).
…
With the BinaryFileResponse
, you can still set the Content-Type
of the sent file, or change its Content-Disposition
:
On the same page there is a bit of information on the Response Header Bag as well but minimal and using a code example:
$response->setContent('Hello World'); // the headers public attribute is a ResponseHeaderBag $response->headers->set('Content-Type', 'text/plain'); $response->setStatusCode(Response::HTTP_NOT_FOUND);
It states the public header attributes are the response header bag.
Hi Jasper Frumau, thank you for helping us with these wonderful post!
I have a little question, what if we want to call a controller in Tinker that receives a Request class as parameter?? Like: public function show(MyRequestClass $request){}