Package Data | |
---|---|
Maintainer Username: | reshadman |
Maintainer Contact: | pcfeeler@gmail.com (Reza Shadman) |
Package Create Date: | 2017-08-20 |
Package Last Update: | 2021-08-23 |
Language: | PHP |
License: | MIT |
Last Refreshed: | 2024-12-21 03:01:03 |
Package Statistics | |
---|---|
Total Downloads: | 1,795 |
Monthly Downloads: | 5 |
Daily Downloads: | 0 |
Total Stars: | 113 |
Total Watchers: | 2 |
Total Forks: | 7 |
Total Open Issues: | 0 |
Get rid of anything related to files in Laravel, This package handles all for you.
Handles your public assets (.css, .js, .etc) you can use your CDN provider to serve the static assets. with a simple function call. For instance you can serve your static files through Rackspace CDN. After each deploy a new unique id is assigned to the path so the cached assets would be purged.
Image manipulation and storage: Store all of your images in the cloud (based on Laravel Adapters, Rackspace, S3, minio etc.), The image resizing is handled with simple configuration. You define templates and then images are generated automatically. Once a new image template created, you can use the nginx directives included in the package to remove the participation of PHP in next calls. Read the documentation for more info. A simple, fast and reliable method to manipulate images that are stored in the cloud.
Detects redundant files, File names are generated based on the filesize + a hash function. so redundant files could not exist technically, You can implement your own file name generator, too.
Storing files with a simple method call. They can be served without the participation of PHP, and they can be addressed with the package's helper functions if they are public.
Database Tracking (Optional), Centerialized Eloquent model which tracks your stored files(and images), You can store files/resiable-images and attach them to your bussiness models. Then you can use them. If it is an Image the templates are accessible by simple getters in the model.
Simple helper functions for dealing with resizable image urls, file urls, asset urls etc.
A Simple controller for serving private/public files Serve both resizable images, and files. You can implement your own access control for restricting access on request. So for instance if the file should be only served to its uploader, you can implement an access controller which checks that the requested file is attached to the user model or not.
https://12factor.net offers some practical specs for dealing with files, called attached resources. As files are an important part of the most of the information systems, they should be stored in a reliable, fast third party service (Like Amazon S3, or Rackspace object storage).
This package is an implementation of an spec for making Laravel application 12factor compatible for attached resources. You can follow the spec with your own implementation.
So if your application domain is not about files (You are not Dropbox or Amazon S3 itself :D). You can follow the spec and use the features this package offers, There are typically some main usecases for files:
Laravel file-secretary offers some simple solutions for the above needs. We did not apart the package to individual ones to respect the simplicity. The interface that this package offers could be much simpler and more performant as Laravel file-secretary has been developed periodically it is not in its simplest shape. We will keep that in mind for next releases.
Run the following command in your project directory, to add the package to composer.json
:
composer require reshadman/file-secretary ">=1.0.0 <1.1.0"
Add the Service Provider to your config/app.php
<?php
return [
// other app.php config elements
'providers' => [
// Other providers
// ...
\Reshadman\FileSecretary\Infrastructure\FileSecretaryServiceProvider::class
]
];
Then publish the configuration file:
php artisqan vendor:publish \
--provider=Reshadman\FileSecretary\Infrastructure\FileSecretaryServiceProvider \
--tag=config
If you want to use the eloquent model for attaching files to your models, export the migration:
php artisqan vendor:publish \
--provider=Reshadman\FileSecretary\Infrastructure\FileSecretaryServiceProvider \
--tag=migrations
Almost everything is handled by configuration, For understanding how this package works please read the documentation blocks in the default config file here:
The best way to see the usage is by reading the integration tests, however you may read the following doc to understand what it does.
Contexts: file-secretary uses contexts for detecting where to store the files based on laravel filesystem drivers,
we have four context categories basic_file
, image
, manipulated_image
and assets
,
all contexts should have a laravel filesystem driver, and a folder name in the driver.
When you command to store the file in a context, the equivalent, laravel disk driver is found by the config
and the starting path(folder of the context) is considered as the directory. Also generating file URLs is handled by this config.
Context Category: basic_file
: a basic file is a simple file that does not need
any manipulation, when defining contexts you can indicate a basic_file
context,
and when you command to store the file, they will be added in the context's laravel filesystem
driver and the given folder. Files can be served with or without the participation of PHP.
Context Category: image
: Images that should be manipulated and mutated based on your given config.
Storing manipulateable images is not different from storing simple files, except that
instead of a unique file name, they are stored in a unique directory,
so the main image and its manipulated children are always in that unique folder.
You can also have different context strategies for the main image and its manipulated children.
You can indicate that the manipulated images of the main image should not be stored at all or be stored
beside the main image, or be stored in a different context (as manipulated images are not critical they can
be stored in more cost effective storage like your own server).
Context Category manipulated_image
: This is a context that is used for image
context as the place
to store its manipulated images.
Context Category: asset
: If you want, you can upload your entire built asset directory to your cloud CDN provider,
like Rackspace's public CDN.
Asset Folders/Tags: These are the folders that you want to upload to the cloud, in your blade templates
by calling fs_asset('assetFolderName', 'your_local_path_to.css')
you can address them, assets are purged on each call, so the
browser won't serve the old versions.
Image templates: Templates are objects that keep the responsibility for mutating and manipulating images You may use the default generic template (which dynamically re-sizes, strips images with different config), or implement your own one. They are defined in the config.
Database/Eloquent Tracked Files: After storing a file in a context you may assign it to the centralized eloquent model, this model can be attached to other business models, like the profile image of a user.
File/Folder name: in the context of this package, a file name is a unique id
which is generated automatically, it needs to be unique in the context of your app.
You can implement your own file name generator. By default it is based on the sha1(fContent) + filesize
, It guarantees
That the same file is not redundant in the context.
Serving Files/Images/Manipulated Images: Simply you may serve files publicly or privately, There is an HTTP endpoint in this package
which will serve the requested files/images, It retrieves the equivalent context
and file_name
from the requested URL,
and downloads the file from the storage of the found context and serves it to the user.
Before downloading, It calls the privacy object of the found context, if it returns false, it will throw an HTTP
400 Exception. You can define your privacy classes for the found context, which will be discussed in the documentation.
You can read the default config('file_secretary.contexts')
element of the config file to see all the available
options for creating contexts.
Context must be unique in terms of Laravel filesystem disks + the starting folder in the disk (the
context_folder
).
To serve your static assets through a public CDN, like Rackspace public CDN, you create an asset context like below:
<?php return [
// Other file secretary config elements...
'contexts' => [
// Other contexts...
'assets_context' => [
'category' => \Reshadman\FileSecretary\Application\ContextCategoryTypes::TYPE_ASSET,
'driver' => 'rackspace_asset_disk',
'context_folder' => 'some_context_folder',
'driver_base_address' => 'some-unique-string.rackcdn.com/etc/',
]
],
'asset_folders' => [
'backoffice' => [
'path' => public_path('backoffice-assets'),
'context' => 'assets_context',
// fills .env automatically like => BACKOFFICE_ASSET_ID=unique-id
// which causes to the browser to not serve old versions.
'env_key' => 'BACKOFFICE_ASSET_ID',
],
]
];
Other things will be handled automatically, in development environment, the assets will be served
local and in production env they will be served from the given driver_base_address
To sync the latest assets run:
php artisan file-secretary:upload-assets --tags=backoffice
To address the assets in the template call:
<?php
$url = fs_asset('backoffice', 'styles.dist.css');
dump($url);
// In development env: http://localhost:8000/backoffice/styles.dist.css
// In production env: some-unique-id.rackcdn.com/[context_folder]/[latest-unique-id]/styles.dist.css
To delete the old versions:
By default the last two version are kept, and other versions are deleted after a successful upload.
For a different strategy you may change the following event listener in file_secretary.php
:
<?php
return [
// Other config file_secretary config elements
'listeners' => [
\Reshadman\FileSecretary\Application\Events\AfterAssetUpload::class => [
'\YourListener\Class',
]
],
];
file-secretary takes care of storing files after they have been validated by you. Then they can be tracked and served.
You can pass different file targets to the store command. For storing a file you should create an instance of the following class:
<?php
\Reshadman\FileSecretary\Application\PresentedFile::class;
To see the list of available file targets read the contents of the above class.
The
PresentedFile
class support different file types inluding: URL, File Path, File Content and File Instance ,read the following docs.
You can not control the file names, they are used for tracking files and images.
For the files of a basic_file
context, the storing format would be something like this:
https://driver_base_address.mycdn.com/[context_folder]/[unique-id-xxxx-xxxxx].[file_extension]
For the files of a image
or manipulated_image
context the file name would be some thing like this:
# Main file
https://driver_base_address.mycdn.com/[context_folder]/[unique-id-xxxx-xxxx]/1_main.[image_extension]
# Manipulated templates:
https://driver_base_address.mycdn.com/[context_folder]/[unique-id-xxxx-xxxx]/[my_template_200x200.[image_extension_or_template_extension]
You can define your own unique id generator in the config:
<?php
return [
'file_name_generator' => \OpensslRandomFileNameGenerator::class,
// O1ther config elements...
];
?>
<?php
use Reshadman\FileSecretary\Application\FileUniqueIdGeneratorInterface;
use Reshadman\FileSecretary\Application\PresentedFile;
class OpensslRandomFileNameGenerator implements FileUniqueIdGeneratorInterface {
public static function generate(PresentedFile $presentedFile)
{
$length = 128;
return bin2hex(openssl_random_pseudo_bytes($length / 2));
}
}
?>
Please note that your closure should always return a unique string.
The function prevents redundant files in the same context.
After you have created the PresentedFile
instance you should pass it to the store command.
For knowing all the constructor parameters of the PresentedFile
class read the class implementation.
For finalizing the storage read the following:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
'SOME_TEXT_CONTENT_HERE_', // The mime type will be detected automatically.
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_CONTENT
);
/** @var \Reshadman\FileSecretary\Application\Usecases\StoreFile $storeCommand */
$storeCommand = app(\Reshadman\FileSecretary\Application\Usecases\StoreFile::class);
$addressableRemoteFile = $storeCommand->execute($presentedFile);
dd($addressableRemoteFile->fullRelative(), $addressableRemoteFile->fullUrl());
If you have the a local file path, you can create the PresentedFile
instance like the following:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
$path = '../path_to/my_file.png',
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_PATH,
basename($path)
);
If you have the file content, you can create the PresentedFile
instance like the following:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
file_get_contents($path = '../path_to/my_file.png'), // The mime type will be detected automatically.
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_CONTENT
);
If you want to store the file from an instance of request UploadedFile
or a Symfony file instance
read the following
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
request()->file('company_logo'), // The mime type will be detected automatically.
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_INSTANCE
);
If you want to store a file from a URL you can read the following:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
'https://logo_url.com/logo.png', // The mime type will be detected automatically.
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_URL
);
If you want to store a file from a base64 string read the following:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'file_manager_private', // context name
'base64encodecontent=', // The mime type will be detected automatically.
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_BASE64
);
Note that if other meta data is attached to the string it should be removed by you, the mime type will be detected automatically.
After executing the store file command it will return an instance of:
<?php
\Reshadman\FileSecretary\Application\AddressableRemoteFile::class;
for knowing methods read the class implementation.
Storing images is not different from storing files. You should only pass the proper
context_name
which has image
category to the PresentedFile
instance.
new PresentedFile( 'context_name', '/path/to/file', PresentedFile::FILE_TYPE_PATH, "optional_original_file_name.pdf" );
To delete a file you should use the following service command:
<?php
/** @var \Reshadman\FileSecretary\Application\Usecases\DeleteFile $deleter */
$deleter = app(\Reshadman\FileSecretary\Application\Usecases\DeleteFile::class);
$fileFullRelativePath = '/context_folder/unique-id.pdf';
$deleter->execute("context_name", $fileFullRelativePath);
If your deleting an image context and it stores templates, the manipulated images with be deleted, too.
When dealing with cloud file storages, which some of them offer CDN services, it is not a good idea
to update a file, you can simply delete the old one and create a new one, that is because it takes some times
that the CDN provider fetch the new version for all edges. And because we do not focus on file names
in our implementation it is better to leave the file in the storage, or add a X-DELETE-AFTER
header to that,
or delete it entirely and create a new file.
If you have some reason that this functionality is needed please create a PR or open an issue.
Please not that if you implement your own update strategy, do not use the built in file name generator function, because the file name is based on file content, so if you change the file content with the same file name, it will be overridden next time the old file is uploaded to the context.
After you have created a context with category
, image
file-secretary allows to manipulate and mutate images based on image templates,
you define image templates in the config file, and when the image is requested through the url
with predefined parameters, the main parent image is fetched from the disk storage, the image content
is passed to the template object and the template's result is served to the user.
The image templates are defined for all image
contexts.
To define your image templates you should add your config to the available_image_templates
of the config file:
<?php return [
'companies_logo_200x200' => [
'class' => \Reshadman\FileSecretary\Infrastructure\Images\Templates\DynamicResizableTemplate::class,
'args' => [
'width' => 200,
'height' => 200,
'encodings' => null, // When null only parent file encoding is allowed.
'strip' => false, // removes the ICC profile when imagick is used.
],
],
];
By using the following class as the class
parameter of your image template config, you can control the
behaviour with args, this will cover most of your needs:
<?php
\Reshadman\FileSecretary\Infrastructure\Images\Templates\DynamicResizableTemplate::class
The class
parameter is the template implementation, each template can have some arguments,
the DynamicResiableTemplate
get some arguments which allow you to satisfy most of your needs.
for resizing:
['png', 'jpg', 'svg']
, if null
the same encoding as the parent image will be used.file-secretary uses intervention package for image manipulation. To write your own template you should create a class that implements the following interface
<?php
\Reshadman\FileSecretary\Infrastructure\Images\TemplateInterface::class;
Then you can use it in the config, also you can check the DynamicResiableTemplate
implementation for faster understanding.
Manipulated images are requested through some URL, this URL is automatically generated by the
package if for instance your attaching the profile_image
to users
model in eloquent.
The URL carries the following info with itself:
The endpoint retrieves the above info from the request and creates the following object
<?php
\Reshadman\FileSecretary\Application\PrivacyCheckNeeds::class;
The object contains all the needed information for fetching the parent image, manipulating it, storing it, and serving the manipulated image.
After the image has been manipulated, the context config says to whether store the manipulated image
in the cloud, or ignore it, or store it somewhere else, in a less expensive disk,
all of these can be defined by setting the value of store_manipulated
in the config arary of the context.
There are 3 available values for store_manipuled
:
false
: It won't store the manipulated image.true
: Stores it beside the manipulated image.another_context_name
: the target context should be of type ContextCategoryTypes::TYPE_MANIPULATED_IMAGE
so the parent file for example is stored in Amazon S3, all the manipulated images are stored in your own FTP
server which is wrapped around an Nginx proxy. This allows to serve manipulated images without the participation
of PHP, once they are created, and as they are not the only data source, nothing happens when they are deleted
accidentally.For more info about this options read the contexts
section of the default config file.
If your domain models (like your User
model) has some blob files, for example the avatar of the user,
file-secretary offers a centralized, file model which keeps tracks of your cloud files.
So after you uploaded your file to the proper context you may create a tracked record for that and attach the record to your user's model through a foreign key.
You should also include the packages migration to create the database table.
Anytime you need to attach a cloud file to an eloquent business model,
The eloquent model has some getter methods that generate the image template URLs and file URLs, automatically.
For more info view the exported migration of the package.
When storing files the process is not different from storing the file in the cloud,
You create an instance of PresentedFile
and pass it to the following service, the created record is returned instead:
<?php
$presentedFile = new \Reshadman\FileSecretary\Application\PresentedFile(
'user_images_context',
request()->file('avatar_file'),
\Reshadman\FileSecretary\Application\PresentedFile::FILE_TYPE_INSTANCE,
basename(request()->file('avatar_file')) // Is stored in the table so you can show the client's name if needed.
);
/** @var \Reshadman\FileSecretary\Application\Usecases\StoreTrackedFile $storeTrackedFile */
$storeTrackedFile = app(\Reshadman\FileSecretary\Application\Usecases\StoreTrackedFile::class);
$trackedModel = $storeTrackedFile->execute($presentedFile);
auth()->user()->avatar()->attach($trackedModel);
dump($trackedModel->full_url);
echo "<img src='{$trackedModel->image_templates['avatar_50x50']}' title='{$trackedModel->original_name}'/>";
By default the following implementation is used:
<?php
\Reshadman\FileSecretary\Application\EloquentPersistedFile::class;
You can extend the above class and create your own implementation:
<?php return [
// Other file_secretary.php config elements
'eloquent' => [
'model' => '\YourOwnModel\Class',
'table' => 'defined_table_in_model_or_this_string_in_fallback'
]
];
The package does not delete the file from cloud when you delete the tracked record, this is for preventing unexpected behaviour.
You can delete the file manually by the following service:
<?php
/** @var \Reshadman\FileSecretary\Application\EloquentPersistedFile $trackedFile */
$trackedFile = auth()->user()->avatar;
/** @var \Reshadman\FileSecretary\Application\Usecases\DeleteTrackedFile $deleter */
$deleter = app(\Reshadman\FileSecretary\Application\Usecases\DeleteTrackedFile::class);
$deleter->execute($trackedFile);
// You can also delete a file by uuid
$deleter->execute($trackedFile->getFileableUuid());
There 3 strategies when calling the delete service:
To indicate this you should execute the service like the following:
<?php
use Reshadman\FileSecretary\Application\Usecases\DeleteTrackedFile;
/** @var \Reshadman\FileSecretary\Application\EloquentPersistedFile $trackedFile */
$trackedFile = auth()->user()->avatar;
/** @var \Reshadman\FileSecretary\Application\Usecases\DeleteTrackedFile $deleter */
$deleter = app(\Reshadman\FileSecretary\Application\Usecases\DeleteTrackedFile::class);
$deleter->execute($trackedFile, DeleteTrackedFile::ON_DELETE_DELETE_REMOTE);
$deleter->execute($trackedFile, DeleteTrackedFile::ON_DELETE_IGNORE_REMOTE);
$deleter->execute($trackedFile, DeleteTrackedFile::ON_DELETE_DELETE_IF_NOT_IN_OTHERS);
This package handles file serving for you based on the config you've given in the contexts
section and the
available_image_templates
section.
However you may serve the files the way you are satisfied with. For instance if only admins are allowed to view some doc files you can create a route for that check that the current user is admin or not, then retrieve the file id from the request parameters and serve it to the user:
<?php
use Reshadman\FileSecretary\Application\EloquentPersistedFile;
use Reshadman\FileSecretary\Infrastructure\FileSecretaryManager;
class SomeController extends \Illuminate\Routing\Controller {
public function getDocument($fileId, FileSecretaryManager $fManager)
{
if (!auth()->user()->is_admin) {
abort(403);
}
/** @var EloquentPersistedFile $trackedFile */
$trackedFile = EloquentPersistedFile::findOrFail($fileId);
$diskDriver = $fManager->getContextDriver($trackedFile->getFileableContext());
$path = $trackedFile->full_relative_path;
$contents = $diskDriver->get($path);
if ($contents === false) {
abort(404);
}
$headers = ['Content-Type' => $diskDriver->mimeType($path)];
return response($contents, 200, $headers);
}
}
If you provide a driver_base_address
for contexts, the URLs will be generated with prepended base_address
So if your files are public you can simply define the driver_base_address
of your disk, and then
they are served directly from the disk HTTP endpoint.
There is a predefined route which file-secretary provides:
<?php
route('file-secretary.get.download_file', [
'context_name' => '',
'context_folder' => '',
'after_context_path'
]);
This route handles file serving for all contexts except the asset
contexts.
If you don't want this routes to be include in your package, disable it with setting its key to false
in config.
<?php return [
// Other config elements
'load_routes' => false
];
There is a default controller which you can use to attach it to your desired routes:
<?php
\Reshadman\FileSecretary\Presentation\Http\Actions\DownloadFileAction::class;
There is a middleware included in the package which fetches the needed data to retrieve files from the cloud if you are using your own routes with the default controller, this middleware should be wrapped around the route:
<?php
\Reshadman\FileSecretary\Presentation\Http\Middleware\PrivacyCheckMiddleware::class;
You can decide that the given user is allowed to view the requested file or not, with defining privacy classes in the config, read the config file as the documentation.
The file identifiers are passed to your privacy class and then you can decide that the user is allowed to get this file or not.
All of the privacy classes should implement the following interface:
<?php
\Reshadman\FileSecretary\Application\Privacy\PrivacyInterface::class;
The restriction works only if you use the built-in controller class for serving.
You can use an array pipeline for manipulating response headers on serving. Each array item should be an implementation of:
<?php
\Reshadman\FileSecretary\Presentation\Http\HeadersMutatorInterface::class;
The headers array is passed to each header mutator item defined in config.
For details on how to use please see the public_images
section of the config file.
If you have public contexts, you can use the driver_base_address
functionality to remove the participcation of PHP
for basic_file
context category, the base address will be prepended to the relative path, and it will be served
directly.
for image
context category, the main image is always served from the base address, however the image templates
are still served from PHP, you can create an Nginx reverse proxy which does one of the following:
Set store_manipulated
to true
, Create an Nginx directive that will call the laravel endpoint
on file not found of the base address, the laravel endpoint will store the file beside the main so the in the next call
the image is served from your upstream base address.
Set store_manipulated
to false
Use a reverse proxy with caching, which calls the laravel endpoint,
after a successful call Nginx will cache the image, so the image will be served from Nginx's cache.
Set store_manipulated
to another context which uses a local disk, set the disk path as root for Nginx and
on 404, call the laravel endpoint, or make the route path and disk path look the same, So it will be routed
to the generator controller automatically when the file does not exist.
Nginx Directives will be added to the package soon. To make it much more simpler.
Will be added soon.
Will be added soon.
There are integration tests written for this package. To run integration tests do as the following:
Create your phpunit.xml
file based on the packages's phpunit.dist.xml
: cp phpunit.dist.xml phpunit.xml
Fill the phpunit config with your environment variables.
The package has been tested with Rackspace Object storage, to prove the
functionality in cloud. You can change the phpunit.xml
file and the configs in fixtures/config/
to integrate them with your testing environment.
Run the tests with vendor/bin/phpunit --debug
Currently there is no isolated object unit testing for this package. They will be added in next releases.
This package has been extracted from jobinja.ir - The leading job board and career platform in Iran, This is part of the work for making jobinja.ir, 12factor.net compatible.
The MIT License (MIT). Please see License File for more information.