Recently I've been working with a friend on a project in which users have profile pictures.
We used Jetstream to get this functionality out of the box. Although it provided us with almost everything that we needed in that aspect, one thing was missing: uploading pictures from URLs.
Long Story Short
I created the laravel-url-uploaded-file package that extends Laravel's UploadedFile
class to allow its instantiation from a URL instead of an actual file that was uploaded from the user's computer.
The package can be installed using Composer:
composer require naxon/laravel-url-uploaded-file
The usage is pretty simple and straightforward:
use Naxon\UrlUploadedFile\UrlUploadedFile;
$file = UrlUploadedFile::createFromUrl('https://naxon.dev/assets/img/portrait.jpg');
Now you have an UrlUploadedFile
instance that can use all of the functionality that's in the base UploadedFile
class. For example, you can store the file:
$file->storeAs('pics', 'profile-pic.' . $file->extension());
Using with Jetstream's Profile Photos
Jetsteam uses the HasProfilePhoto
trait that exposes three methods: updateProfilePhoto
, deleteProfilePhoto
and getProfilePhoto
. No need to explain, pretty straightforward, right?
I wanted to have another updateProfilePictureFromUrl
that, well, updates the user's profile picture from a url instead of an uploaded file. In order to achieve this, I needed to override the base HasProfilePhoto
and add my new method to it:
<?php
namespace App\Models\Concerns;
use Naxon\UrlUploadedFile\UrlUploadedFile;
use Laravel\Jetstream\HasProfilePhoto as BaseTrait;
trait HasProfilePhoto
{
use BaseTrait;
public function updateProfilePhotoFromUrl(string $url)
{
$this->updateProfilePhoto(UrlUploadedFile::createFromUrl($url));
}
}
The updateProfilePhoto
method receives an UploadedFile
instance as its first argument, and because UrlUploadedFile
extends UploadedFile
, I can simply pass it to the method and have Jetstream take care of the process without overriding / duplicating any of its functionality.
Pretty simple, right?
Under The Hoods
As mentioned before, UrlUploadedFile
extends Laravel's UploadedFile
class file, which extends a class with the same name from Symfony and adds Laravel-specific functionality to it, such as storing and mocking.
Due to our rule of sticking with Laravel's standards and style, we looked for a quick way to instantiate UploadedFile
from a URL by mocking a file upload. At this point I remembered that I actually used something similar in the past while working with Spatie's great package Laravel Media Library. This package allows you to associate media to a model from URLs. After reviewing the package's source code, I came across this Downloader
.
So let's take a look on the UrlUploadedFile
class and break down its functionality:
<?php
namespace Naxon\UrlUploadedFile;
use Illuminate\Http\UploadedFile;
use Naxon\UrlUploadedFile\Exceptions\CantOpenFileFromUrlException;
class UrlUploadedFile extends UploadedFile
{
public static function createFromUrl(string $url, string $originalName = '', string $mimeType = null, int $error = null, bool $test = false): self
{
if (! $stream = @fopen($url, 'r')) {
throw new CantOpenFileFromUrlException($url);
}
$tempFile = tempnam(sys_get_temp_dir(), 'url-file-');
file_put_contents($tempFile, $stream);
return new static($tempFile, $originalName, $mimeType, $error, $test);
}
}
If you expected to see some very long code, then I'm sorry to disappoint you. The code is pretty straightforward, and here's what is does:
- Open the file using
fopen
. - Create a file with
tempnam
in the system directory for temporary files (sys_get_temp_dir
).url-file-
is just a prefix that's used to distinguish files created by the package. - Fill the temporary file with the content of the file that was opened at the beginning of the method, using
file_put_contents
. - Return a new instance of
UrlUploadedFile
with our temporary file. This is possible because Symfony'sUploadedFile
is instantiated with a file path.
I created this package to answer a specific need in one of my projects. I didn't test it thoroughly with other file types. In order to extend its functionality, other downloaders may be needed. My focus for the next version will be to support custom downloaders, like Spatie are doing in their Media Library package.