Cache issue with dynamic parameter in config.yml - php

I have a multisite aplication with a log system depends on each site and I want that each of them has its own log file so I did that :
1) I use a dynamic variable in config.yml with depends on $_SERVER['HTTP_HOST'].
config.yml:
imports:
- { resource: param.php }
...
monolog:
handlers:
user:
type: stream
path: %kernel.logs_dir%/%domain_name%.%log_filename%
channels: [user]
=> %domain_name% is my dynamic variable
2) I set it in app/config/param.php:
<?php
$url = $_SERVER['HTTP_HOST'];
$domain = preg_replace('/^www./', '', $url);
$container->setParameter('domain_name', $domain); // this set my variable `%domain_name%` with the current domain (ex: site1.com)
Problem :
This code works fine but when I switch to an other site, the value of %domain_name% remains equal to its initial value.
Exemple :
I am in www.site1.com => %domain_name% value is "site1.com" so it works
fine
I switch to www.site2.com => %domain_name% value is always "site1.com" and
not "site2.com"
Why ?
Cache issue. Indeed, Symfony load in cache config.yml so it keeps the first value of %domain_name%
To delete this value I have to execute this command: rm -rf app/cache/* but it is not a solution.
So nobody has a solution for this problem ? Thanks

We had the same problem in our multi-site CMS environment. Symfony is primarily designed to run a single site, so the config.yml gets run once, and then cached. That means if you inject dynamic parameters into the config.yml (by any means: Globals, Constants, Apache Environment, etc), after the first run they are cached for each subsequent request :( So the next unique domain site request, will be using the cached values from the first site. In most cases, this creates problems.
Some people have suggested creating different environments for each site. But that's not practical when you host hundreds, thousands or millions of sites.
Alternative Cache Location
At the moment, the best solution we can conceive is to specify a different cache folder for each site. So you extend the app/appKernel.php and customize the getCacheDir function.
//app/appKernel.php
class AppKernel extends Kernel
{
public function getCacheDir()
{
return $this->rootDir.'/cache/'.$this->environment.'/'.$_SERVER['HTTP_HOST'];
}
}
See API Reference for Kernel.

I am not quite sure, if you are using the parameter file/configuration as intended.
For me there is information missing, to answer your question directly, so here are the two possible scenarios:
1) Site www.site1.com and www.site2.com is pointing at the SAME source (same server):
In this case I wouldn't define the domain_name as an configuration value, course it is not an distinct configuration parameter it is more like a variable. Build a service or think about defining an additional environment.
2) www.site1.com and www.site2.com are two distinct projects:
In that case you shouldn't deploy the contents of the cache directory (which you should do anyway; but in this case it would be the issue ;)).
EDIT
You could implement different configuration files to solve:
config_site1.yml # Configuration for www.site1.com
config_site2.yml # Configuration for www.site2.com
Controller in your /web directory:
app_site1.php
app_site2.php
initialize the AppKernel with the requested environment, and point to the controller in your vhost config or your htaccess file.
I think you could change the app.php controller even to something like:
[...]
require_once __DIR__.'/../app/AppKernel.php';
$request = Request::createFromGlobals();
$kernel = new AppKernel($request->giveMeMyHostEnvironmentMappingShizzle(), false);
$kernel->loadClassCache();
[...]
kind regards

Related

Access to the base_path of a particular Asset directory from a controller

I want to access the base_path (base_url registered) of a specific Asset component directory from a controller in order to store my reports to a specific path preconfigured in config.yml.
I started changing my configuration, after upgrading to Symfony 2.7, like the following:
app/config/config.yml
framework:
assets:
version: 'v5'
version_format: '%%s?version=%%s'
base_path: ~
base_urls: ['http://cdn.example.com', 'https://secure.example.com']
packages:
reports:
base_path: bundles/mybundle
So, when I request a specific route, with the correct request parameters my controller generate the HTML from a particular Twig template and, at the end, it will be converted to PDF using KnpSnappyBundle.
At last, my purpose is to build a list of generated PDF reports accessible from a public assets directory.
$kernel->locateResource()
However, I can access the complete path using a workaround like the following:
$this->container->get('kernel')->locateResource('#MyBundle/Resources/public/reports')
Using parameters.yml
I have also asked for some hints and it seems legit to use the parameters.yml in order to manage the Asset component configuration. So, from the controller, they would be accessed using $this->getParameter() and, at the same time, as a configuration value for Asset.
The simplest way to deal with that is to define it as a parameter in parameters.yml, as you suggested yourself.
It's really easy to get it and it totally makes sense.
Update
I wanted to provide a bit more reasoning for my answer, so I will cite http://symfony.com/doc/current/best_practices/configuration.html as a reference.
Reading there, it seems that you should put into "parameters.yml" all infrastructure parameters which do not really change your application behaviours. I think this applies to your case as well: your application does not change its behaviour according to assets paths, it only needs to know where they are.
So, again, I'd say that putting them in parameters.yml not only provides you an easy solution but also it's a "good practice".

Symfony2: Customize database connection, cache and log folders, based on incoming request

I am trying to build a multi-tenanted solution, using an existing codebase, but with segregated databases, cache and log files.
An effective way of doing this would be to dynamically compute the following, based on an incoming requests domain name:
database connection parameters
cache folder
log folder
In Symfony1.x, I could do this by creating a custom filter class, but I don't know how to do this in Symfony2.
My question then is this:
How may I modify the above parameters for a symfon2 application - based on an incoming request?
I agree with Jurgis Gregov.
You should change in your web/app.php line:
$kernel = new AppKernel('prod', false);
to:
$kernel = new AppKernel($_SERVER['HTTP_HOST'], false);
The first argument of AppKernel is environment name
After that you can create in app/config a new config files with name:
config_name.yml
Where you can set doctrine configuration.
Your cache will be in folder: app/cache/name
And your logs will be in: app/logs/name.log (You can also set path to log in config yml file)
I don't know if there is other way to change doctrine parameters but i know that you can also chanage logs and cache folder by override methods:
getLogDir() and getCacheDir() in your app/AppKernel.php where you can use REQUEST to set it.

Testing multi-tenancy Laravel applications using Behat / Mink and Behat Laravel Extension

I am building a multi-tenant SaaS application which I am trying to write tests for with Behat, using Mink and the Behat Laravel Extension
When you register for an account, you get your own subdomain on the site {account}.tenancy.dev
my behat.yml file looks like so:
default:
extensions:
Laracasts\Behat:
# env_path: .env.behat
Behat\MinkExtension:
default_session: laravel
base_url: http://tenancy.dev
laravel: ~
I am having problems straight off the bat as when I try to test my registration flow, I am getting a 404 error testing that the new subdomain is accessible, all of the data has been saved correctly, manually testing the process works and the subdomain routing works.
I was wondering if there was any way to do this using Behat and how I would go about setting Behat / Mink to use wildcard subdomains to test SaaS applications?
I am running the test inside the Homestead VM.
The base_url: http://tenancy.dev configuration is used to generate a fully qualified domain URL when you utilize relative path URL's in your mink steps (IE "/home").
When you want to hit a domain different from the domain specified in base_url, all you have to do is use the fully qualified domain URL in your step like "http://test.tenancy.dev/fully/qualified".
So use the base_url configuration to set what you will be using for the majority of your steps as relative url's and then explicitly specify the full domain for the exceptions.
When I create an account named foo
And GET "http://foo.tenancy.dev/ping"
Then I get a 200 response code
When I GET "/home"
Then the response contains "Sign Up"
If the majority of your testing will be against the sub domain, set that as your base_url and explicitly specify your top level domain when necessary.
You may resolve subdomains using xip.io, which is especially useful if you cannot access the /etc/hosts file on a CI server, for example.
To route {account}.tenancy.dev to your local webserver, you can use account.tenancy.dev.127.0.0.1.xip.io which resolves to 127.0.0.1.
After a short while I revisited this problem and found a rather simple solution to be used in my FeatureContext.php:
$this->setMinkParameter('base_url', $url);
This changes the base url for any scenario it is used in:
/**
* #Given I visit the url :url
*/
public function visitDomain($url)
{
$this->setMinkParameter('base_url', $url);
$this->visit('/');
}
Which is used in the following way:
Scenario: Test Multi Tenancy
Given I have a business "mttest"
When I visit the url "http://mttest.example.com"
Then I should see "mttest"
Obviously this is slightly contrived but does show that what I was intending to do is possible.

How to change different configuration settings between environments in PHP?

I have a a few php files which I call via AJAX calls. They all have a URL to my config.php. Now I've the problem that I always have to change the URLs to that config file by hand when I deploy a new version on my server.
Local Path:
define('__ROOT__', $_SERVER["DOCUMENT_ROOT"].'/mywebsite');
Server Path:
define('__ROOT__', $_SERVER["DOCUMENT_ROOT"].'/../dev.my-website.tld/Modules/');
I want to track changes in all of these PHP files. I'm searching for a solution to automatically change this path.
E.g.
This is my current workflow:
Local Environment:
(path version A)
do changes in the code
git add, git commit, git merge, git push to my server
Server:
git reset --hard
change path to version B
You are trying to run different code bases between development and live, which is not recommended -- they should be identical. The way I tackle this is to use an environment variable to specify which of several config files should be loaded.
In my Apache vhost I do something like this:
SetEnv ENVIRONMENT_NAME local
And then I use a function to read the environment name:
function getEnvironmentName()
{
$envKeyName = 'ENVIRONMENT_NAME';
$envName = isset($_SERVER[$envKeyName]) ? $_SERVER[$envKeyName] : null;
if (!$envName)
{
throw new \Exception('No environment name found, cannot proceed');
}
return $envName;
}
That environment name can then be used in a config file to include, or to retrieve values from a single array keyed on environment.
I often keep environment-specific settings in a folder called configs/, but you can store them anywhere it makes sense in your app. So for example you could have this file:
// This is /configs/local.php
return array(
'path' => '/mywebsite',
// As many key-values as you want
);
You can then do this (assuming your front controller is one level deep in your project, e.g. in /web/index.php):
$root = dirname(__DIR__);
$config = include($root . '/configs/' . getEnvironmentName() . '.php');
You'll then have access to the appropriate per-environment settings in $config.
A pure git way to achieve this would be filters. Filters are quite cool but often overlooked. Think of filters as a git way of keyword expansion that you could fully control.
The checked in version of your file would for example look like this:
define('__ROOT__', 'MUST_BE_REPLACED_BY_SMUDGE');
Then set up two filters:
on your local machine, you'd set up a smudge filter that replaces
'MUST_BE_REPLACED_BY_SMUDGE'
with
$_SERVER["DOCUMENT_ROOT"].'/mywebsite'
on your server, you'd set up a smudge filter that replaces
'MUST_BE_REPLACED_BY_SMUDGE'
with
$_SERVER["DOCUMENT_ROOT"].'/../dev.my-website.tld/Modules/'
on both machines, the clean filter would restore the line to be
define('__ROOT__', 'MUST_BE_REPLACED_BY_SMUDGE');
Further information about filters could be found in this answer and in the Git Book.

Laravel Application Environment - Best practice and security advice

I am planning on building a multi-tenant application in Laravel with a master subdomain holding the relevant public files and a subdomain for each customer who will have their own databases pointing to the 'master' files. I am planning on doing this as automated as possible e.g. you click a button a subdomain is created, a database is created and the relevant config files are set. All this is fine except I'm not sure if my practices are the best to use and whether or not there are any security issues with it.
In the bootstrap/start.php file I have the following:
$env = $app->detectEnvironment(array(
$_SERVER['SERVER_NAME'] => array($_SERVER['SERVER_NAME'])
));
This would essentially mean that the environment for test.example.co.uk is test.example.co.uk. My install script will create a config directory 'test.example.co.uk` in 'app/config' and will add the relevant database config there.
This does all work as I expected so I am just looking for advice, are there any vulnerabilities with this?
Just to Add - Users will not be able to use the installation script, its just for the developers
I don't think there is any security issues with your code. One thing that I notice is that you are limiting youself to just one environment. Here is my env settings:
$env = $app->detectEnvironment(function()
{
return getenv("ENV") ? : "local";
});
Now my environment will be auto detected - on server I did provide "hook"
in the form of getenv function, and on local machine it is local.
Also instead of array, I am sending callback to detectEnvironment - for more flexibility.

Categories