Download a file from command line in laravel artisan - php

I am using laravel Artisan Commands to download a file from public folder .
Actually i want to download a zip file which i have successfully made in public folder.
I am able to download the file when I call the methods on button click.
But when I execute the command in console it does not work and also not throw any error.
public function downloadExportRawData() {
$a=public_path().'/temp/rawexport/rawexportproduct.zip';
$path='/temp/rawexport/rawexportproduct.zip';
if(file_exists('temp/rawexport/rawexportproduct.zip')){
return Response::download($a);
}else{
return "hello";
}
}
Tried this also...
public static function downloadZip($rand_folder, $zipname, $fileName) {
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="' . $fileName . '.zip"');
header('Content-Length: ' . filesize($zipname));
$handle = fopen($zipname, 'rb');
//exec("rm -r ".$temp);
while (!feof($handle)) {
echo fread($handle, 1048576);
ob_flush();
flush();
}
fclose($handle);
// exec("rm -r " . $rand_folder);
}
and the command is
php artisan product:export --from=http://localhost/valuable-app/dev-master/rest_app/public/ --productid=5b273b0e4bb5995b108b4576 --tokenexport=354c3ddb2bd45aa6e5c4f749749b0b58cbacef48
The main problem is that it is going through the function in controller but not downloading it.
And I tried to echo the response it prints code in zip form means a encoded form which have something related to my zip file means its printing the zip not downloading it.
I am using Artisan first time. Any idea how to download that file using using user-defined Artisan command.
I am trying this from last two weeks but not able to do anything.

How about this:
First identify the file which you want to download and then call
string shell_exec ( string $cmd )
you can use wget to get the file.
for example, in your command
$fileName = $this->argument('filename');
$file = url('storage/public/' . $fileName);
shell_exec("wget $file");
$this->info('downloading');

if you'r using the console, the http download will not work (you are not using a browser).
Instead you can move the file to your wished destination or something like that, but not download it via console

console will not handle http response as download (will not save it)
I think you should consider to add second argument in your console:
--destination=/home/user/downloads/filetosaveas.extension and in your code use curl to download
or more simple use core curl:
curl http://example.com --output my.file --silent

It would be nice if you actually went through the nicely done Writing Commands guide at the Laravel Docs site, here.
In a nutshell, there are mainly two ways to register artisan command. Be it that one is an alternative to the other.
(Like routes) specify your instructions within a closure.
(Like controllers) create a command class and specify your instructions.
The closure way
Inside my routes/console.php, I specify something like below:
Artisan::command('download:file', function () {
// your code goes here.
})->describe('Download a file.');
Now execute a help routine for your newly created command.
-> php artisan help download:file
Description:
Donwload a file.
Usage:
donwload:file
//...
The command way
Generate the command class using php artisan make:command EmailFileCommand. This command will create app/Console/Commands/EmailFileCommand.php. Update the file to specify what we want it to do.
// ...
class EmailFileCommand extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'email:file';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Email a file.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
// your code goes here.
}
}
Trigger commands via HTTP
Say you want to call the commands via an http request. In my routes/web.php file,
//...
use Artisan;
//...
Route::get('/download', function () {
Artisan::call('download:file');
});
Route::get('/email', function () {
Artisan::call('email:file');
});
//...
Now execute your http requests /download and /email.
To download a file, you can use Laravel's Filesystem.
//...
use Storage;
//...
Storage::download('file.jpg')
This post has been extracted from my blog post here.

One can use the shell_exec() command to run a terminal command through PHP function or script. It will execute the command without opening the terminal window. the behaviour of command will be little bit different from the original command.

Related

PHP - send command line output to dynamically named files

I have an application which is built in CakePHP 3.
It uses Console Commands to execute several intensive processes in the background using cron.
The application consists of 5 individual commands:
src/Command/Stage1Command.php
src/Command/Stage2Command.php
src/Command/Stage3Command.php
src/Command/Stage4Command.php
src/Command/Stage5Command.php
These can be executed manually by running each one individually, e.g. to execute Stage1Command.php:
$ php bin/cake.php stage1
To make them run via Cron, I created a 6th command (src/Command/RunAllCommand.php) which goes through these in order.
// src/Command/RunAllCommand.php
class RunAllCommand extends Command
{
public function execute(Arguments $args, ConsoleIo $io)
{
$stage1 = new Step1Command();
$this->executeCommand($stage1);
// ...
$stage5 = new Stage5Command();
$this->executeCommand($stage5);
}
}
This works fine so I can now execute everything with 1 command, php bin/cake.php run_all, which will be added as a cron task to automate running the 5 processes.
The problem I'm having is that each of the 5 commands (Stage1Command ... Stage5Command) produces output which appears on standard output in the console.
I need to be able to write the output produced by each of the 5 commands individually into dynamically named files.
So I can't do something like this
$ php bin/cake.php run_all > output.log
Because
output.log would contain everything, i.e. the output from all 5 commands.
output.log isn't a dynamic filename, it has been entered manually on the command line (or as the output destination of the cron task).
I looked at Redirecting PHP output to a text file and tried the following.
Added ob_start(); to RunAllCommand.php:
namespace App\Command;
ob_start();
class RunAllCommand extends Command { ... }
After executing the first task (Stage1Command) capturing ob_get_clean() to a variable called $content:
$stage1 = new Step1Command();
$this->executeCommand($stage1);
$content = ob_get_clean();
When I var_dump($content); it comes out as an empty string:
string(0) ""
But the output is still produced on the command line when executing php bin/cake.php run_all (RunAllCommand.php).
My plan for the dynamic filename was to generate it with PHP inside RunAllCommand.php, e.g.
// $id is a dynamic ID generated from a database call.
// This $id is being generated inside a foreach() loop so is different on each iteration (hence the dynamic nature of the filename).
$id = 234343;
$filename_stage1 = 'logs/stage1_' . $id . '.txt'; // e.g. "logs/stage1_234343.txt"
Then write $content to the above file, e.g.
file_put_contents($filename_stage1, $content);
So I have 2 problems:
The output is being echoed to the console, and unavailable in $content.
Assuming (1) is fixed, how to "reset" the output buffering such that I can use file_put_contents with 5 different filenames to capture the output for the relevant stage.
On each command file you could use the LogTrait then output what file is outputting before any commands to seperate what command is logging or setup the log config with different scopes to output to different files. example of outputting to the cli-debug.log file.
use Cake\Log\LogTrait;
class Stage1Command extends Command
{
use LogTrait;
public function execute(Arguments $args, ConsoleIo $io)
{
$this->log('Stage 1 Output: ', 'debug');
//do stuff
$this->log('output stage 1 stuff', 'debug');
}
}
I have two suggestions for solving your issue.
Option 1 - Using shell_exec
shell_exec returns a string of the output, so you can write it to a log file directly.
public function execute(Arguments $args, ConsoleIo $io)
{
$stage1_log = shell_exec('bin/cake stage1 arguments');
file_put_contents('stage1_dynamic_log_file.txt', $stage1_log);
$stage2_log = shell_exec('bin/cake stage2 arguments');
file_put_contents('stage2_dynamic_log_file.txt', $stage2_log);
}
Option 2 - Overwrite the ConsoleOut stream
Alternatively a more CakePHP style would be to call the command slightly differently. If you look at the contents of executeCommand() it does a few checks and then calls command->run($args, $io)
Also if you look at how the ConsoleIo is constructed, we can override the output method so instead of using php://stdout we could use a file instead, if you look at the code for ConsoleOutput it's just using normal fopen and fwrite.
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOutput;
public function execute(Arguments $args, ConsoleIo $io)
{
// File names
$id = 234343;
$filename_stage1 = 'logs/stage1_' . $id . '.txt';
// Create command object
$stage1 = new Stage1Command();
// Define output as this filename
$output = new ConsoleOutput($filename_stage1);
// Create a new ConsoleIo using this new output method
$stage1_io = new ConsoleIo($output);
// Execute passing in the ConsoleIo with text file for output
$this->executeCommand($stage1, ['arguments'], $stage1_io);
}

Laravel Artisan: Returns 0 for Python output

I have written one python utility program which returns an array of the result. This utility takes an image path as an input, process that image and in return gives an json array of the result.
Now I want to show this output on my website which is using Laravel 5.7
So I created one artisan command which executes python script and gives me the result.
This command works well in terminal but when I execute this same command from my controller using Artisan::call method, it shows me "0" as the output in the web browser.
I tried shell_exec() too. But the result is the same.
Here is my Artisan Command File
$process = new Process('python3 my_python_script.py -i ' . $this->argument('path'));
$process->run();
$result = $process->getOutput();
return $result;
Artisan Command
php artisan image:upload image_path
The call method of Artisan returns integer. It doesn't read from output or it is not your return value from handle method.
See:
/**
* Run an Artisan console command by name.
*
* #param string $command
* #param array $parameters
* #param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* #return int
*/
public function call($command, array $parameters = [], $outputBuffer = null);
Maybe you should try Artisan::output() but I don't think that it is thread safe or something like that.

Log Laravel with artisan output

I'm working with Laravel and I trying to do a log of some function output with this Log function:
Log::warning("some message")
But if I want to write in console and log file I need to write two times the same message with different function like this:
Log::warning("some message") //This work good for log file
dump("some message") //This work good for artisan console output
There is some function that allow me only use one of both?
You can use Laravel events to integrate this functionality without requiring any change to the way you currently log information.
Add a listener for the Illuminate\Log\Events\MessageLogged event, and within your listener output the log entry to the console if the request has come from the console -- using runningInConsole().
Register a new listener called MessageLoggedListener, e.g:
protected $listen = [
'Illuminate\Log\Events\MessageLogged' => [
'App\Listeners\MessageLoggedListener',
],
];
Generate your listener with php artisan event:generate
Add the event handler to your listener:
/**
* Handle the event.
*
* #param MessageLogged $event
* #return void
*/
public function handle(MessageLogged $event)
{
if (app()->runningInConsole()) {
$output = new ConsoleOutput();
$output->writeln("<error>{$event->message}</error>");
}
}
That's it! You're now ready to test the functionality. From a console command log a message, e.g:
public function handle()
{
Log::error('Hello world! This is an error.');
}
This is the output you'll see:
$ php artisan command
Hello world! This is an error.
And within your log file you'll see:
[2018-01-15 16:55:46] local.WARNING: Hello world! This is an error.
You can improve the functionality by adding different output styles, for example you may wish to use error for errors and info for info. You can read about styling your output here in the Symfony documentation.

Monolog does not work in Laravel Console on server

I'm having problems with logging some basic info line from Console. Locally, this works fine, everything gets logged (errors, info messages) from web and from console. But on server, error logging works fine, but when i call logger myself and try to log some info message it does not work.
I'm using Laravel 5.4 version.
This is some App\Console\Commands\DummyCommand class for test purposes.
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Log\Writer as Log;
class DummyCommand extends Command
{
/**
* Create a new console command instance.
*
* #param \Illuminate\Log\Writer $log
* #return void
*/
public function __construct(Log $log)
{
parent::__construct();
$this->log = $log;
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$message = "Some dummy message";
$this->info($message); // gets printed on console both locally and on server
$this->log->info($message);
$ret = $this->log->getMonolog()->addInfo($message);
dump($ret); // locally = true, server = false
if ($ret === false) {
throw new \Exception('Error while logging!'); // this log works fine both locally and on server
}
}
}
I run the following on the server:
php artisan dummy // prints "Some dummy message" and loggs error message "Error while logging!"
php artisan schedule:run // same thing as above
Any idea why is this not working ?
Permissions on storage folder is fine for all folders and files. Error messages (exceptions) gets logged but info messages does not.
Edited:
This is working, I need to add following line before calling $this->log->info(); but there is no way i would use on all Commands and every time need to set path to log file. (source: https://laracasts.com/discuss/channels/general-discussion/logging-in-l5/replies/11771)
$this->log->useDailyFiles(storage_path().'/logs/laravel.log');
Figured it out, I added multiple message type:
$this->log->getMonolog()->addAlert($message);
$this->log->getMonolog()->addCritical($message);
$this->log->getMonolog()->addDebug($message);
$this->log->getMonolog()->addError($message);
$this->log->getMonolog()->addWarning($message);
And it logs all messages with error prefix (everything except info). So I looked in .env file, and there was next line about log level:
APP_LOG_LEVEL=notice
Changed that to "debug" and now it's working fine.

PHP webhook script (bitbucket)

I'm trying to attempt using a webhook script so that I can just commit locally and have the script triggered server-side and pull in any change.
Now if I login to the server via SSH and run php webhook.php the cript is triggered successfully and the files are updated. So I know the file does work. But if I make edits to the files, commit and push to master, I'm not seeing the files updated.
The log file produced by the script suggests everything is fine, but clearly it's not.
My file structure is like this:
var
www
my-project
- webhook.php
repo-folder
So the file should be pulling the files into repo-folder and the webhook.php is set as the webhook via the bitbucket control panel. If I view the log within bitbucket, it's showing a successful request every time I push a commit.
The script:
<?php
date_default_timezone_set('Europe/London');
class Deploy {
/**
* A callback function to call after the deploy has finished.
*
* #var callback
*/
public $post_deploy;
/**
* The name of the file that will be used for logging deployments. Set to
* FALSE to disable logging.
*
* #var string
*/
private $_log = 'deployments.log';
/**
* The timestamp format used for logging.
*
* #link http://www.php.net/manual/en/function.date.php
* #var string
*/
private $_date_format = 'Y-m-d H:i:sP';
/**
* The name of the branch to pull from.
*
* #var string
*/
private $_branch = 'master';
/**
* The name of the remote to pull from.
*
* #var string
*/
private $_remote = 'origin';
/**
* The directory where your website and git repository are located, can be
* a relative or absolute path
*
* #var string
*/
private $_directory;
/**
* Sets up defaults.
*
* #param string $directory Directory where your website is located
* #param array $data Information about the deployment
*/
public function __construct($directory, $options = array())
{
// Determine the directory path
$this->_directory = realpath($directory).DIRECTORY_SEPARATOR;
$available_options = array('log', 'date_format', 'branch', 'remote');
foreach ($options as $option => $value)
{
if (in_array($option, $available_options))
{
$this->{'_'.$option} = $value;
}
}
$this->log('Attempting deployment...');
}
/**
* Writes a message to the log file.
*
* #param string $message The message to write
* #param string $type The type of log message (e.g. INFO, DEBUG, ERROR, etc.)
*/
public function log($message, $type = 'INFO')
{
if ($this->_log)
{
// Set the name of the log file
$filename = $this->_log;
if ( ! file_exists($filename))
{
// Create the log file
file_put_contents($filename, '');
// Allow anyone to write to log files
chmod($filename, 0666);
}
// Write the message into the log file
// Format: time --- type: message
file_put_contents($filename, date($this->_date_format).' --- '.$type.': '.$message.PHP_EOL, FILE_APPEND);
}
}
/**
* Executes the necessary commands to deploy the website.
*/
public function execute()
{
try
{
// Make sure we're in the right directory
chdir($this->_directory);
$this->log('Changing working directory... ');
// Discard any changes to tracked files since our last deploy
exec('git reset --hard HEAD', $output);
$this->log('Reseting repository... '.implode(' ', $output));
// Update the local repository
exec('git pull '.$this->_remote.' '.$this->_branch, $output);
$this->log('Pulling in changes... '.implode(' ', $output));
// Secure the .git directory
exec('chmod -R og-rx .git');
$this->log('Securing .git directory... ');
if (is_callable($this->post_deploy))
{
call_user_func($this->post_deploy, $this->_data);
}
$this->log('Deployment successful.');
}
catch (Exception $e)
{
$this->log($e, 'ERROR');
}
}
}
// This is just an example
$deploy = new Deploy('/var/www/site-name/repo-name');
$deploy->execute();
?>
i was researching this more and the best way you can do is like i mention find out what is the user being used by apache server by building a php script and run the shell_exec('whoami'); and run on your browser to see that user it is.
Then on your document root for your website mine per example is /var/www you would need to create shh keys for this directory when you do that also add a config file with the host bitbucket and reference to the key you created.
Add the key to bitbucket
then you would need to add permission for apache to run the git command run the command visudo and add:
yourapacheuser ALL=(yourapacheuser) NOPASSWD: /usr/bin/ #the /usr/bin in my case is where i have installed git so you need to see what is the directory path where you install git.
After that you should be able to run your script without problems, in case it complains about a requiretty message then on visudo again add: Defaults:yourapacheuser !requiretty
hope it helps
did you configure SSH keys for the server you are using in bitbucket and add the webhook with the url of your script?
As user1361389 wrote you need to know what users are running the different processes. This is how I did on an Amazon Ubuntu instance.
I have a php file that calls a bash script:
shell_exec("sudo -u ubuntu /home/ubuntu/gitpull.sh");
Create SSH keys for user ubuntu and uploaded the public key to bitbucket.
Also, make sure that the php files on your server are owned by the correct user. In my case ubuntu.
You then need to impersonate ubuntu when calling the php file to deploy code. Add this line in the sudoer file (>sudo visudo )
www-data ALL=(ubuntu) NOPASSWD: /path/to/gitpull.sh
Then in bitbucket add the URL to your hook.

Categories