Lumen - Change default Storage path - php

I'm trying to find out how to change the default storage location (including it's subfolders) on a Lumen project. For several reasons, given the current configuration of the production web server, Lumen throws a permission denied exception when trying to write logs or compile Blade views.
The only alternative, without involving the sysadmin, is to move the storage folder to a tmp folder on the webserver.
On laravel there seems to be a method called "useStoragePath", but it doesn't seem to be available on Lumen (5.2.x).
The default paths seem to be "hardcoded", I found this:
Project\vendor\laravel\lumen-framework\src\Application.php
/**
* Get the storage path for the application.
*
* #param string|null $path
* #return string
*/
public function storagePath($path = null)
{
return $this->basePath().'/storage'.($path ? '/'.$path : $path);
}
And for the logs (same file):
/**
* Get the Monolog handler for the application.
*
* #return \Monolog\Handler\AbstractHandler
*/
protected function getMonologHandler()
{
return (new StreamHandler(storage_path('logs/lumen.log'), Logger::DEBUG))
->setFormatter(new LineFormatter(null, null, true, true));
}
Bottom line: Is there any clean way of overriding the default storage path keeping in mind this restrictions?:
It should not involve the sysadmin (sym links, changing permissions, etc.)
Not tampering with the vendor folder.

On Line 286 of vendor/laravel/lumen-framework/src/helpers.php:
if (! function_exists('storage_path')) {
/**
* Get the path to the storage folder.
*
* #param string $path
* #return string
*/
function storage_path($path = '')
{
return app()->storagePath($path);
}
}
The key here is this line:
if (! function_exists('storage_path'))
That means if a function named storage_path hasn't already been defined, then Lumen will use its own implementation.
All you have to do is simply write your own function that returns your own custom path.
Because Lumen has far fewer rules than Laravel, how you do this is entirely up to you. That said, I would suggest doing it the following way:
Place a file called helpers.php under your app directory
Add any and all custom helper functions into this file including your own storage_path implementation
Make sure this file is loaded before Lumen itself. In order to do that, you need to place your require statement before composer's autoloader. This can be done at the very first line under bootstrap/app.php:
require_once __DIR__ . '/../app/helpers.php';
require_once __DIR__ . '/../vendor/autoload.php';
try {
(new Dotenv\Dotenv(__DIR__ . '/../'))->load();
} catch (Dotenv\Exception\InvalidPathException $e) {
//
}
....

Related

How to import and register class from a list of service providers at runtime?

Are you able to solve this problem? help me
Procedure
There is a folder called 'plugins' in the base path of laravel;
Inside that folder there are only subfolders that are the plugins;
In the subfolder where the plugin is, there is a single file to be imported called PluginNameServiceProvider.php;
The PluginNameServiceProvider.php in turn will be registered by a main service provider, let's say MainServiceProvider.php, but for that, the PluginNameServiceProvider class must be available/accessible for use;
Problem
The user loads the subfolder containing the 'plugin' in 'zip' format through a GUI, then the system extracts the loaded 'zip' file and directs it to the appropriate location, no similar file exists before that;
When the 'composer update or composer dump-autoload' command is executed the PluginNameServiceProvider class is resolved and can be used elsewhere, but not before that;
The aforementioned composer commands are executed at fixed times, however, the PluginNameServiceProvider class must be available immediately, not being able to depend on composer under penalty of preventing the use of the plugin;
What was tried
A temporary autoloader was created that searches for classes not yet resolved by composer as below, however, it is not working
try{
/**
* For information on 'spl_autoload_register' see: https://www.php.net/manual/en/function.spl-autoload-register.php
* For information on 'glob()' see: https://www.php.net/manual/en/function.glob.php
* For information on 'app()' see: https://laravel.com/docs/9.x/helpers#method-app
* base_path() returns the path of the plugins folder, something like: 'D:\www\laravel\plguins'
* $name returns the namespace of the plugin's service provider, something like: '\plugins\plugin-name\Providers\PluginNameServiceProvider'
* 'File::' is an abbreviation for the facade 'Illuminate\support\facades\File'
**/
//Import file if class not found
spl_autoload_register(function ($name) {
//Check if the file exists before importing it
if(File::exists(base_path($name). '.php')){
include(base_path($name). '.php');
}
});
//Access the plugins folder and list the directories
$folders = File::directories(base_path('plugins'));
//Iterates over a set of subfolders and accesses each one of them looking for a file with 'Provider.php' in the name
foreach($folders as $item){
//Returns the absolute path of the first service provider it finds
$str = collect(glob(base_path('plugins'). "\\". $item . '\\Providers\\*Provider.php'))->first();
//Proceed with registration if the plugin has a service provider, otherwise continue the loop
if(!is_null($str)){
//Prepare the class name and namespace to be used
$filter = pathinfo($str);
$path = $filter['dirname'] .'\\'. $filter['filename'];
$newclass = str_replace('.php', '', strstr($str, '\plugins'));
//Register the plugin provider
app()->register(new $newclass(app()));
}
}
} catch(\Exception $e){
dd($e->getMessage());
}
The problem with my code is that it is not registering the service providers whose namespace has not yet been mapped by the composer. The problem seems to be in spl_autoload_register, I say it seems because the code is in a place that 'kills' laravel in case of a fatal error so the debug options are minimal and the code cannot be moved to another place as it is included inside a package.
Current behavior
The service provider seems to have been imported due to the fact that 'include()' returns 1, however, there are two types of situations: 1) It gives the error 'class not found'; 2) It doesn't display any errors and it doesn't execute the service provider code either.
Expected behavior
The code must import the service provider and register it, so that the code present in that service provider is executed.
Plugin service provider example to be registered
<?php
namespace plugins\plugin-name\Providers;
use Illuminate\Support\ServiceProvider;
class PluginNameServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
dd(['status' => 'Registered!', 'more' => app()]);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
}
I solved the problem with the modification below:
Before
//Returns the absolute path of the first service provider it finds
$str = collect(glob(base_path('plugins'). "\\". $item . '\\Providers\\*Provider.php'))->first();
After
//Returns the absolute path of the first service provider it finds
$str = collect(glob($item . '\\Providers\\*Provider.php'))->first();
I'll leave the code there in the question in case someone tries to implement a hook functionality in php and has a similar problem. If you use it, be kind and give proper credit.

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.

Display/Download a file from server using Symfony2 and jQuery

Using Symfony2.0 and jQuery
My application generates and stores PDF outside the ./www folder as it is private information (comercial invoices for the purchases within my shop).
I am trying and not getting to allow the customer to download their invoices at any time.
I found this article, but at the beginning it talks about how to get them from the "public" ./www folder. At the end, it offers a piece of code, but I think it is not supported by my version of Symfony:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class OfflineToolController extends Controller
{
/**
* #return BinaryFileResponse
*/
public function downloadAction()
{
$path = $this->get('kernel')->getRootDir(). "/../downloads/";
$file = $path.'my_file.zip'; // Path to the file on the server
$response = new BinaryFileResponse($file);
// Give the file a name:
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT,'my_file_name.zip');
return $response;
}
}
This is the case: How to create a link to download generated documents in symfony2?
Any idea of how to do this?

Making Zend-Framework run faster

What are the best ways to make Zend-Framwork run faster besides Zend Optimizer?
If I remember correctly, parsing .ini files in PHP takes a long time. Therefor I cache it (the file won't change during a request)
Are there any other ways to improve ZF's performance?
I cache my application.ini Like this:
Make sure you have the following directory (your cache dir): /application/data/cache
I extend Zend_Application with My_Application, see code:
<?php
require_once 'Zend/Application.php';
class My_Application extends Zend_Application
{
/**
* Flag used when determining if we should cache our configuration.
*/
protected $_cacheConfig = false;
/**
* Our default options which will use File caching
*/
protected $_cacheOptions = array(
'frontendType' => 'File',
'backendType' => 'File',
'frontendOptions' => array(),
'backendOptions' => array()
);
/**
* Constructor
*
* Initialize application. Potentially initializes include_paths, PHP
* settings, and bootstrap class.
*
* When $options is an array with a key of configFile, this will tell the
* class to cache the configuration using the default options or cacheOptions
* passed in.
*
* #param string $environment
* #param string|array|Zend_Config $options String path to configuration file, or array/Zend_Config of configuration options
* #throws Zend_Application_Exception When invalid options are provided
* #return void
*/
public function __construct($environment, $options = null)
{
if (is_array($options) && isset($options['configFile'])) {
$this->_cacheConfig = true;
// First, let's check to see if there are any cache options
if (isset($options['cacheOptions']))
$this->_cacheOptions =
array_merge($this->_cacheOptions, $options['cacheOptions']);
$options = $options['configFile'];
}
parent::__construct($environment, $options);
}
/**
* Load configuration file of options.
*
* Optionally will cache the configuration.
*
* #param string $file
* #throws Zend_Application_Exception When invalid configuration file is provided
* #return array
*/
protected function _loadConfig($file)
{
if (!$this->_cacheConfig)
return parent::_loadConfig($file);
require_once 'Zend/Cache.php';
$cache = Zend_Cache::factory(
$this->_cacheOptions['frontendType'],
$this->_cacheOptions['backendType'],
array_merge(array( // Frontend Default Options
'master_file' => $file,
'automatic_serialization' => true
), $this->_cacheOptions['frontendOptions']),
array_merge(array( // Backend Default Options
'cache_dir' => APPLICATION_PATH . '/data/cache'
), $this->_cacheOptions['backendOptions'])
);
$config = $cache->load('Zend_Application_Config');
if (!$config) {
$config = parent::_loadConfig($file);
$cache->save($config, 'Zend_Application_Config');
}
return $config;
}
}
And I change my index.php (in the public root) to:
<?php
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
// Define application environment
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path(),
)));
/** My_Application */
require_once 'My/Application.php';
// Create application, bootstrap, and run
$application = new My_Application(
APPLICATION_ENV,
array(
'configFile' => APPLICATION_PATH . '/configs/application.ini'
)
);
$application->bootstrap()
->run();
Reload the page and you see the ini file cached. Good luck.
Parsing .ini files may be a little slow, but I wouldn't expect that to be anywhere near the slowest part of a typical ZF application. Without seeing any results, it seems like including a bunch of files (Zend_Cache_*) may in some cases be even slower than parsing a simple .ini file. Anyway, that's just one area...
ZF has published a good guide on optimization:
http://framework.zend.com/manual/en/performance.classloading.html
In short,
Utilize caching where it matters: database queries / complex operations, full page cache, etc.
Strip require_once calls in favor of auto-loading as per the documentation.
Cache PluginLoader file/class map
If you want to get a bit more into it,
Skip using Zend_Application component
Enable some sort of op-code cache
Do other typical PHP optimization methods (profiling, memory caching, etc.)
see also http://www.kimbs.cn/2009/06/caching-application-ini-for-zend-framework-apps/
Why did you deleted your last question?
I had a good link for you:
I heard things like this before, but
the combination is often related to
migration from one platform to other.
Check at this link:
http://devblog.policystat.com/php-to-django-changing-the-engine-while-the-c

zf create project path name-of-profile file-of-profile

I was not able to find a good resource which is describing the following Zend_Tool command:
zf create project path name-of-profile file-of-profile
Not even here:
http://framework.zend.com/manual/en/zend.tool.usage.cli.html
Does somebody know a good resource regarding this command?
Note: I'm interested in the name-of-profile and file-of-profile part. Usage, examples, etc.
Maybe even a visual approach like in this references:
http://marklodato.github.com/visual-git-guide/index-en.html
http://osteele.com/archives/2008/05/commit-policies
I am not familiar enough with the internals of ZF Tool Project, but have a look at
http://framework.zend.com/manual/en/zend.tool.project.create-a-project.html
http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Tool/Project/Provider/Project.php
Afaik (which is not much) Zend Tool maintains an XML file to keep track of your project. This is required for any subsequent actions to be applied correctly to your project through Zend Tool.
The DocBlock for the create action in the Project Provider says:
/**
* create()
*
* #param string $path
* #param string $nameOfProfile shortName=n
* #param string $fileOfProfile shortName=f
*/
When run without the two optional arguments, the method will eventually create a new project file with
$newProfile = new Zend_Tool_Project_Profile(array(
'projectDirectory' => $path,
'profileData' => $profileData
));
with $profileDate being the content of the default configuration file. If you specify $fileOfProfile, you can override the configuration file and supply your own file, e.g.
if ($fileOfProfile != null && file_exists($fileOfProfile)) {
$profileData = file_get_contents($fileOfProfile);
}
Obviously, you have to supply a full path to the file for this to work. The alternative is to supply a file identifier, which Zend Tool will then try to find in a predefined location, e.g.
$storage = $this->_registry->getStorage();
if ($profileData == '' && $nameOfProfile != null && $storage->isEnabled()) {
$profileData = $storage->get('project/profiles/' . $nameOfProfile . '.xml');
}
I have no clue what the storage part is about. Like I said, I am not familiar with Zend Tool's inner workings. If I understand correctly, you can use the additional two arguments to load an existing project in a new location or customize the default one.
You might want to browse the ChangeLog to find out more about it.

Categories