How to use functions in services.php laravel config file - php

I needed to use a dynamic callback url for socialite so I added the url() function to my services.php file it worked fine(and its still working on my live server) But when tried to start the project locally I get the following error. When I remove the url() method everything works fine please help.
PHP Fatal error: Uncaught ReflectionException: Class log does not exist in /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php:734
Stack trace:
#0 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php(734): ReflectionClass->__construct('log')
#1 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php(629): Illuminate\Container\Container->build('log', Array)
#2 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(697): Illuminate\Container\Container->make('log', Array)
#3 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php(849): Illuminate\Foundation\Application->make('log')
#4 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php(804): Illuminate\Container\Container->resolveClass(Object(ReflectionParameter))
#5 /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php(7 in /home/fenn/projects/jokwit/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 734
Here is my services.php file
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Stripe, Mailgun, Mandrill, and others. This file provides a sane
| default location for this type of information, allowing packages
| to have a conventional place to find your various credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
],
'mandrill' => [
'secret' => env('MANDRILL_SECRET'),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),
'region' => 'us-east-1',
],
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
'facebook' => [
'client_id' => '1700935300171729',
'client_secret' => 'XXXXXXXXXXXXXXXXXXX',
'redirect' => url('/facebook/callback'),
],
'google' => [
'client_id' => 'XXXXXXXXXXXXXXXXXXXXXXXX',
'client_secret' => 'XXXXXXXXXXXXXXXXXXXXXXXX',
'redirect' => url('google/callback'),
],
];

In services.php file
...
'redirect' => 'google/callback',
...
Next create service provider for example ConfigServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ConfigServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
\Config::set("services.google.redirect", url(\Config::get('services')['google']['redirect']));
}
}
now should work fine

It doesn't work because in production Laravel cached all configuration files and using this cache. In development environment Laravel didn't create cache.
You can check it by commenting url() in config and then running php artisan config:cache command. Uncomment url() part and you'll see error is disappeared.
The best you can do here is to not use Laravel or manually defined functions in config files and find another solution for your problem.

Related

Laravel Websockets raises a BroadcastException "Invalid auth signature provided"

I'm trying to add websockets support to my Laravel 9 application. I've read in their documentation that I could run an open source server on my own instead of relying on commercial solutions. With that in mind, I decided to give Beyondcode's Laravel Websockets a try. My application runs on PHP 8.2, using Laravel 9.19 and Laravel Websockets 1.13, communicating with the sockets through the Pusher Channels HTTP PHP Library 7.2.
Laravel Websockets is meant to match the server's settings with the client's by using the same naming scheme on both ends and launching off the same codebase.
This is how I've set up my .env file:
APP_NAME="My app"
APP_ENV=local
APP_KEY=<redacted>
APP_DEBUG=true
BROADCAST_DRIVER=pusher
PUSHER_APP_ID="${APP_NAME}"
PUSHER_APP_KEY="${APP_KEY}"
PUSHER_APP_SECRET="${APP_KEY}${APP_NAME}"
PUSHER_HOST=ws-server
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1
My docker-compose.yml file contains the following content:
version: "3.9"
services:
queue:
build: .
environment:
- ROLE=queue
volumes:
- .:/var/www
ws-server:
build: .
environment:
- ROLE=ws-server
volumes:
- .:/var/www
ports:
- 6001:6001
The ws-server service has an entrypoint script which runs the following command on an infinite loop: php artisan websockets:serve --host 0.0.0.0 --port 6001
Within config/websockets.php I've left everything as it comes out of the box, just so you can rest assured, this is how my apps array looks like:
'apps' => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'path' => env('PUSHER_APP_PATH'),
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
],
]
The queue service hosts a class called TerminalUpdated, which looks like this:
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class TerminalUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public string $id;
public string $newLine;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(string $id, string $newLine)
{
$this->id = $id;
$this->newLine = $newLine;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('console.' . $this->id);
}
}
Another script running within the service mentioned above executes the following sentence:
<?php
$id = '123';
$line = 'This is a line of text.';
TerminalUpdated::dispatch(
$this->id, // id
$line // newLine
);
?>
Everything looks more or less okay to me, yet I'm getting the following exception:
[2023-01-19 01:05:09] local.ERROR: Pusher error: {"error":"Invalid auth signature provided."}. {"exception":"[object] (Illuminate\\Broadcasting\\BroadcastException(code: 0): Pusher error: {\"error\":\"Invalid auth signature provided.\"}. at /var/www/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php:164)
"}
I don't seem to be able to figure out where is it even trying to generate the signature mentioned in the exception shown above.
I've already tried the following possible fixes:
Restart the server
Reset the framework's caches and temporary files by calling php artisan optimize:clear
This is how I configured the WebSocket:
In the websocket.php file :
apps => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'path' => env('PUSHER_APP_PATH'),
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
],
],
In broadcasting.php I have:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'encrypted' => false,
'host'=> '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
],
],
I hope it helps you out.
The problem is that PUSHER_APP_ID can't contain spaces. Using a string without spaces instead fixes the issue. It looks like the library just can't translate the spaces to the expected URL encoding.

Laravel: Serialization of 'Illuminate\Http\UploadedFile' is not allowed

I'm trying to upload an image, store it on disk and add the path to a JSON file that is stored on the database.
every user got a JSON file with all his images like:
{"images": ["/vendors/57/horse-11.png", "/vendors/57/horse-11.png"]}
That JSON file is stored in the database in the 'images' column.
I'm struggling with this issue for days.. I always get following exception:
Serialization of 'Illuminate\Http\UploadedFile' is not allowed
the error occurs in this part of the code from the processImage function:
$image = Image::make($request->file('image'))
->resize(750, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('png');
I will guide you through the proces:
first the user uploads an image, code:
https://codeshare.io/jGzQ9
secondly, via the routes.php a controller is called:
public function update(User $user, UserHouse $house, UserHouseRequest $request){
$path = $this->processImage($request, $user->id, $house->id);
$jsonstring = $house->images;
array_push($jsonstring['images'], $path);
$house->images = $jsonstring;
$house->save();
return back();
}
private function processImage($request, $userId, $houseId)
{
$path = null;
$number = rand(1, 99);
if ($request->hasFile('image')) {
$image = Image::make($request->file('image'))
->resize(750, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('png');
$path = "/users/{$userId}/house-{$houseId}-{$number}.png";
Storage::disk('fileadmin')->put($path, $image->encoded);
}
return $path;
}
There is also some JS code, but I think that that isn't necessary for this problem. If it does, I will add it later.
I'm using Laravel 5.5
stacktrace:
2019-05-28 13:30:49] local.ERROR: Serialization of 'Illuminate\Http\UploadedFile' is not allowed {"userId":57,"email":"info#ho.com","exception":"[object] (Exception(code: 0): Serialization of 'Illuminate\\Http\\UploadedFile' is not allowed at /Users/sg/ah-website/user/laravel/framework/src/Illuminate/Session/Store.php:128)
[stacktrace]
#0 /Users/sg/website/user/laravel/framework/src/Illuminate/Session/Store.php(128): serialize(Array)
#1 /Users/sg/website/user/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(87): Illuminate\\Session\\Store->save()
#2 /Users/sg/website/user/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(218): Illuminate\\Session\\Middleware\\StartSession->terminate(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#3 /Users/sg/website/user/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(189): Illuminate\\Foundation\\Http\\Kernel->terminateMiddleware(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#4 /Users/sg/website/public/index.php(60): Illuminate\\Foundation\\Http\\Kernel->terminate(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#5 /Users/sg/website/server.php(21): require_once('/Users/sg...')
#6 {main}
"}
config/filesystems.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Default Cloud Filesystem Disk
|--------------------------------------------------------------------------
|
| Many applications store files both locally and in the cloud. For this
| reason, you may specify a default "cloud" driver here. This driver
| will be bound as the Cloud disk implementation in the container.
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "s3", "rackspace"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),
'secret' => env('AWS_SECRET'),
'region' => env('AWS_REGION'),
'bucket' => env('AWS_BUCKET'),
],
'fileadmin' => [
'driver' => 'local',
'root' => public_path(),
'visibility' => 'public',
'url' => config('app.url'),
],
],
];

Laravel server hangs whenever I try to request localhost:8000/any using guzzle

If I make any request to http://localhost:8000 or http://127.0.0.1:8000 it hangs on status pending. (Exactly as here https://github.com/guzzle/guzzle/issues/1857)
I was told that it isn't related to guzzle and that I should better ask about it here.
I stumbled upon this problem while following laravel.com/docs/5.4/passport
This is the code that hangs:
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'redirect_uri' => 'http://example.com/callback',
'code' => $request->code,
],
]);
I tried making GET and POST request to working API routes (tested with postman) and it still hangs when calling the same routes using guzzle.
So is there a way to make requests to my own API while using php artisan serve?
Carl has a great solution to this. If you are looking for a quick fix to test your updates - you can get this done by opening up two command prompts. The first would be running php artisan serve (locally my default port is 8000 and you would be running your site on http://localhost:8000). The second would run php artisan serve --port 8001.
Then you would update your post request to:
$response = $http->post('http://localhost:8001/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'redirect_uri' => 'http://example.com/callback',
'code' => $request->code,
],
]);
This should help during your testing until you are able to everything on server or a local virtual host.
try this.
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Route;
use App\User;
class UserController extends Controller
{
//use AuthenticatesUsers;
protected function login(Request $request)
{
$request->request->add([
'grant_type' => 'password',
'client_id' => '3',
'client_secret' => '6BHCRpB4tpXnQvC1DmpT7CXCSz7ukdw7IeZofiKn',
'scope' => '*'
]);
// forward the request to the oauth token request endpoint
$tokenRequest = Request::create('/oauth/token','post');
return Route::dispatch($tokenRequest);
}
}
I ended up solving it by using wamp virtualhost instead of php artisan serve. No idea why it doesn't work with localhost though.
UPDATE: Someone was kind enough to explain why it wouldn't work.
In https://github.com/guzzle/guzzle/issues/1857#issuecomment-506962175
The reason for this is php artisan serve is a single thread application. So when we use guzzle to request from it to itself, it basically just tries to finish guzzle request (as a client) first then come to finish that request (as a server), which is impossible.
More info about this: https://php.net/manual/en/features.commandline.webserver.php
Also this answer:
When making calls to itself the thread blocked waiting for its own reply. The solution is to either seperate the providing application and consuming application into their own instance or to run it on a multi-threaded webserver such as Apache or nginx.
/**
* Login function
*/
public function login(Request $request) {
/*
Sample post object
{
"username": "test#gmail.com",
"password": "test123"
}
*/
if (Auth::attempt(['email' => request('username'), 'password' => request('password')])) {
return $this->getToken($request);
}
else {
return response()->json(['error'=>'Unauthorised'], 401);
}
}
public function getToken(Request $request) {
//Get client ID and client Secret
$client = DB::table('oauth_clients')->where('password_client',1)->first();
$request->request->add([
"grant_type" => "password",
"username" => $request->username,
"password" => $request->password,
"client_id" => $client->id,
"client_secret" => $client->secret,
]);
// Post to "/oauth/token
$tokenRequest = $request->create('/oauth/token','post');
$instance = Route::dispatch($tokenRequest);
//return token_type, expires_in, access_token, refresh_token
return response()->json(json_decode($instance->getContent()));
}

Change S3 Endpoint in Laravel

I used the filesystems.php file to configure my S3.
When I try to put content in the bucket I receive this error:
Encountered a permanent redirect while requesting https://s3-us-west-2.amazonaws.com/MYBUCKET... Are you sure you are using the correct region for this bucket?
I then try to acces the url and I get this message:
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.
On the same page I get:
<Endpoint>s3.amazonaws.com</Endpoint>
Then how could I remove the region from the URL Laravel generates?
You can create a customer service provider like that:
use Illuminate\Support\ServiceProvider;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\Filesystem;
use Aws\Laravel\AwsServiceProvider;
use Storage;
class AwsS3ServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Storage::extend('s3', function($app, $config) {
$client = new S3Client([
'credentials' => [
'key' => $config['key'],
'secret' => $config['secret'],
],
'region' => $config['region'],
'version' => $config['version'],
'endpoint' => $config['endpoint'],
'ua_append' => [
'L5MOD/' . AwsServiceProvider::VERSION,
],
]);
return new Filesystem(new AwsS3Adapter($client, $config['bucket_name']));
});
}
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
//
}
}
And add endpoint variable into config/filesystems.php as well:
's3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),
'secret' => env('AWS_SECRET'),
'region' => env('AWS_REGION'),
'version' => 'latest',
'endpoint' => env('AWS_ENDPOINT'),
'bucket_name' => env('AWS_BUCKET_NAME')
]
Look at docs to get details about how to extend Storage facade.
The simplest approach is to create a new disk that uses the S3 driver in config/filesystems.php. You don't need to create a service provider - the S3 driver will pick up the endpoint from the disk config if supplied.
'spaces' => [
'driver' => 's3',
'key' => env('DO_SPACES_KEY'),
'secret' => env('DO_SPACES_SECRET'),
'endpoint' => 'https://nyc3.digitaloceanspaces.com',
'region' => 'nyc3',
'bucket' => env('DO_SPACES_BUCKET'),
],
Set the DO_SPACES_KEY, DO_SPACES_SECRET and DO_SPACES_BUCKET environment variables to the appropriate values.
Source: https://laracasts.com/discuss/channels/laravel/custom-file-driver-digital-ocean-spaces?page=1
You can use this package https://github.com/aws/aws-sdk-php-laravel
In this package you can specify your settings in config file as desired.
The specific bucket is not created in AWS, so please create a bucket in AWS, and use same bucket at filesystem config, the issue will be resolved.

Add Metadata, headers (Expires, CacheControl) to a file uploaded to Amazon S3 using the Laravel 5.0 Storage facade

I am trying to find out how to add in Metadata or headers (Expires, CacheControl etc.) to a file uploaded using the Laravel 5.0 Storage facade. I have use the page here as reference.
http://laravel.com/docs/5.0/filesystem
The following code works correctly:
Storage::disk('s3')->put('/test.txt', 'test');
After digging I also found that there is a 'visibility' parameter which sets the ACL to 'public-read' so the following also works correctly.
Storage::disk('s3')->put('/test.txt', 'test', 'public');
But I would like to be able to set some other values to the header of the file. I have tried the following:
Storage::disk('s3')->put('/index4.txt', 'test', 'public', array('Expires'=>'Expires, Fri, 30 Oct 1998 14:19:41 GMT'));
Which doesn't work, I have also tried:
Storage::disk('s3')->put('/index4.txt', 'test', array('ACL'=>'public-read'));
But that creates an error where the 'visibility' parameter can not be converted from a string to an array. I have checked the source of AwsS3Adapter and it seems there is code for options but I can not seem to see how to pass them correctly. I think it takes the following:
protected static $metaOptions = [
'CacheControl',
'Expires',
'StorageClass',
'ServerSideEncryption',
'Metadata',
'ACL',
'ContentType',
'ContentDisposition',
'ContentLanguage',
'ContentEncoding',
];
Any help on how to accomplish this would be appreciated.
First, you need to call getDriver so you can send over an array of options. And then you need to send the options as an array.
So for your example:
Storage::disk('s3')->getDriver()->put('/index4.txt', 'test', [ 'visibility' => 'public', 'Expires' => 'Expires, Fri, 30 Oct 1998 14:19:41 GMT']);
Be aware that if you're setting Cache-Control it has to be passed as CacheControl. This may well be true for other keys with non-alphanumierc characters.
If you want to have global defaults with headers, this works in Laravel 5.4. Change your config/filesystems.php file like so:
s3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),
'secret' => env('AWS_SECRET'),
'region' => env('AWS_REGION'),
'bucket' => env('AWS_BUCKET'),
'options' => ['CacheControl' => 'max-age=315360000, no-transform, public',
'ContentEncoding' => 'gzip']
],
After attempting the above answers and failing to be able to add customer user-metadata it turns out that after digging through the SDK code it is a bit easier than I thought (Assume $path is a path to an image file). I didn't appear to need to call the getDriver() method either, not too sure if that makes any difference with the current version of the AWS SDK.
Storage::put(
'image.jpg',
file_get_contents($path),
[
'visibility' => 'public',
'Metadata' => [
'thumb' => '320-180',
],
]
);
So now if you view the newly uploaded file in S3 you will see the custom metadata:
Hope this helps someone.
The answer from #Paras is good. But there is one thing that can confuse newcommers:
'options' => [
'Expires' => gmdate('D, d M Y H:i:s GMT', strtotime('+1 month')),
>>> WRONG visibility' => 'public', WRONG <<<
]
If you want to define global options for the HEADERS, the options array is the right way to go. But if you also want to define the visibility, you can not mix it up. Visibility has to be defined outside of options array.
👍
'visibility' => 'public',
'options' => ['Expires' => gmdate('D, d M Y H:i:s GMT', strtotime('+1 month'))]
This is an example of how to upload a file to S3 as of Laravel 5.8 with expiry and cache control headers, for example:
Storage::put($directory . '/' . $imageName,
$image, [
'visibility' => 'public',
'Expires' => gmdate('D, d M Y H:i:s \G\M\T', time() + (60 * 60 * 24 * 7)),
'CacheControl' => 'max-age=315360000, no-transform, public',
]);
Also don't forget to uncheck the 'Disable cache' checkbox in Chrome if you're testing and it never seems to work, that got me bad for an hour when my browser wouldn't cache things even though I finally got the headers right in S3.
For Laravel 9 users this has became more easy. You do not need to call ->getDriver() anymore. You can directly pass options to the put command.
Storage::disk('s3')->put('/index.txt', 'file content', [
// S3 Object ACL
'visibility' => 'public', // or 'private',
// HTTP Headers
'CacheControl' => 'public,max-age=315360000',
'ContentDisposition' => 'attachment; filename="index.txt"',
'Expires' => 'Thu, 12 Feb 2032 08:24:43 GMT',
// Metadata or other S3 options
'MetadataDirective' => 'REPLACE'
'Metadata' => [
'Custom-Key' => 'test',
],
])
In case you need other headers or options, please checkout the flysystem source code for all available headers and options.
https://github.com/thephpleague/flysystem-aws-s3-v3/blob/master/src/AwsS3Adapter.php#L38
public const AVAILABLE_OPTIONS = [
'ACL',
'CacheControl',
'ContentDisposition',
'ContentEncoding',
'ContentLength',
'ContentType',
'Expires',
'GrantFullControl',
'GrantRead',
'GrantReadACP',
'GrantWriteACP',
'Metadata',
'MetadataDirective',
'RequestPayer',
'SSECustomerAlgorithm',
'SSECustomerKey',
'SSECustomerKeyMD5',
'SSEKMSKeyId',
'ServerSideEncryption',
'StorageClass',
'Tagging',
'WebsiteRedirectLocation',
];
Hey I solved this problem, you need to create a custom S3 filesystem
First, create a new file CustomS3Filesystem.php and save into app/providers, this custom S3 filesystem uses the S3 Adapter, but you can add metadata and headers.
<?php namespace App\Providers;
use Storage;
use League\Flysystem\Filesystem;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v2\AwsS3Adapter as S3Adapter;
use Illuminate\Support\ServiceProvider;
class CustomS3Filesystem extends ServiceProvider {
public function boot()
{
Storage::extend('s3_custom', function($app, $config)
{
$s3Config = array_only($config, ['key', 'region', 'secret', 'signature', 'base_url']);
$flysystemConfig = ['mimetype' => 'text/xml'];
$metadata['cache_control']='max-age=0, no-cache, no-store, must-revalidate';
return new Filesystem(new S3Adapter(S3Client::factory($s3Config), $config['bucket'], null, ['mimetype' => 'text/xml', 'Metadata' => $metadata]), $flysystemConfig);
});
}
public function register()
{
//
}
}
Add provider into providers list at config/app.php
'App\Providers\CustomS3Filesystem',
create new filesistem name in config/filesystems
's3-new' => [
'driver' => 's3_custom',
'key' => 'XXX',
'secret' => 'XXX',
'bucket' => 'XXX',
],
Use the new created custom s3 adapter
Storage::disk('s3-new')->put(filename, file_get_contents($file), public);
I used laravel documentation to customize the s3 adapter
http://laravel.com/docs/5.0/filesystem#custom-filesystems
I hope this may help you.
I am using Laravel 4.2, but I think my solution might also help on Laravel 5.0 (cannot say for sure, as I have not tried to upgrade yet). You need to update the meta options in the config for the Flysystem driver that you are using. In my case, I created a connection called s3static to access the bucket where I am storing images that will not be changing.
My config file:
's3static' => [
'driver' => 'awss3',
'key' => 'my-key',
'secret' => 'my-secret',
'bucket' => 'my-bucket',
// 'region' => 'your-region',
// 'base_url' => 'your-url',
'options' => array(
'CacheControl' => 'max_age=2592000'
),
// 'prefix' => 'your-prefix',
// 'visibility' => 'public',
// 'eventable' => true,
// 'cache' => 'foo'
],
Now when I put any files on to S3 using this connection, they have the Cache-Control meta data set.
To expand on #sergiodebcn 's answer, here is the same CustomS3Filesystem class working for S3 v3 and the latest Laravel. Note I have removed the XML mimetype and set up a 5 day cache time:
namespace App\Providers;
use Illuminate\Support\Arr;
use Storage;
use League\Flysystem\Filesystem;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v3\AwsS3Adapter as S3Adapter;
use Illuminate\Support\ServiceProvider;
class CustomS3Filesystem extends ServiceProvider
{
/**
* Format the given S3 configuration with the default options.
*
* #param array $config
* #return array
*/
protected function formatS3Config(array $config)
{
$config += ['version' => 'latest'];
if ($config['key'] && $config['secret']) {
$config['credentials'] = Arr::only($config, ['key', 'secret']);
}
return $config;
}
/**
* Bootstrap a custom filesystem
*
* #return void
*/
public function boot()
{
Storage::extend('s3_custom', function($app, $config)
{
$s3Config = $this->formatS3Config($config);
return new Filesystem(
new S3Adapter(
new S3Client($s3Config),
$config['bucket'],
null,
[
'CacheControl' => 'max-age=432000'
]
)
);
});
}
public function register()
{
//
}
}
Using Laravel 8 here:
I didn't see this mentioned elsewhere, but the metadata option key => values listed by Christoph Kluge
appear to only accept string values, and fail silently if passed an integer, bool, etc... So if you're passing in a variable you'll need to convert to a string value:
$fileID = $fileData['FileId'];
$fileExt = $fileData['FileExtension'];
$fileUnique = $fileData['UniqueFileId'];
$isImage = $fileData['IsImage'];
$isDefault = $fileData['IsDefaultImage'];
$filePath = $fileUnique . "." . $fileExt;
$file = $mp->fileID($fileID)->get();
if (Storage::disk('s3')->missing('img/' . $filePath)) {
Storage::disk('s3')->put(
'img/' . $filePath,
$file,
[
// Metadata or other S3 options
'MetadataDirective' => 'REPLACE',
'Metadata' => [
'is-image' => strval($isImage),
'is-default' => strval($isDefault),
'unique-file-id' => strval($fileUnique),
'file-extension' => strval($fileExt),
]
]
);
echo nl2br('uploading file: ' . $filePath . "\n");
} else {
echo nl2br('file already exists:' . $filePath . "\n");
}

Categories