Symfony 1.4 project in SAAS mode - multiple databases - php

I have a Symfony 1.4 project that I want to use in SAAS mode for multiple customers.
The idea is to have multiple domains and databases but to always use the same code. I also don't want to have to change anything in my project for each new customer : everything must be dynamic.
I've been trying for a few hours now to override the database.yml configuration but it wasn't really a success. I added at the beginning of my ProjectConfiguration.class.php :
require_once '/path/to/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine.php';
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();
$serverName = $_SERVER['SERVER_NAME'];
if(preg_match("#\.saas-domain\.com$#", $serverName))
$dbname = "db-" . preg_replace("#\.saas-domain\.com$#", "", $serverName);
else
$dbname = "db-default";
$dsn = "mysql:dbname=$dbname;host=localhost";
$user = 'dbuser';
$password = 'dbpsw';
try {
$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection($dbh);
} catch (PDOException $e) {
die("Can't find that database");
}
I tried to put this code at the end of the setup() method, same thing : it doesn't seem to be used.
As you can see I wanted to deduce the database to use from the domain (ServerName in apache).
My very last option is to manually alter each config_database.yml.php file generated in cache (in fact only alter one and use symlinks), but I really would like not to have to do this.
So my questions are :
Is it possible to override the database.yml configuration ?
If not, is there a way I can delete this file or ignore it and set up a manual doctrine configuration ?
Are there any other solutions that I didn't consider ?
EDIT
I managed to come up with a solution (with the help of this article). It's probably not the best way to do it, but at least the result is OK.
The idea is to use Symfony's filters in order to replace the connection created by the database YAML file :
In config/filters.yml add the following :
myDBConnectionFilter:
class: myDBConnectionFilter
Then create this myDBConnectionFilter class and put it anywhere it can be found by Symfony's autoloader (lib in most cases). Below is an example of code you can use in this class :
<?php
class myDBConnectionFilter extends sfFilter
{
public function execute($filterChain){
if ($this->isFirstCall())
{
$serverName = $_SERVER['SERVER_NAME'];
if(preg_match("#\.saas-domain\.com$#", $serverName))
$dbname = "db-" . preg_replace("#\.saas-domain\.com$#", "", $serverName);
else
$dbname = "db-default";
$dsn = "mysql:dbname=$dbname;host=localhost";
$user = 'user';
$password = 'password';
$manager = Doctrine_Manager::getInstance();
try {
$default = $manager->getConnection('dbconnection');
Doctrine_Manager::getInstance()->closeConnection($default);
$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection($dbh, 'dbconnection');
} catch (Exception $e) {
die("Error : " . $e->getMessage());
}
}
$filterChain->execute();
}
}
You'll then need to clear Symfony's cache.
This code retrieve a connection from Doctrine_Manager, close it, then create a new one with the same name to replace it. In this case, the connection is called dbconnection. The name of the connection is the one used in database.yml.
As I said, it is not the best way to do it as this method makes two connections to the database instead of one. So if anyone has a better solution i would take it !

Related

Undefined variable with include or require

I'm trying to include a variable from a different file into my main php file but I keep getting Undefined variable.
accounts.php
# GUEST CREDENTIALS
$host = 'localhost';
$guestAccount = 'oncofinder';
$guestPassword = 'xxxxxx';
$database = 'oncofinder';
#LOGGER CREDENTIALS
$loggerAccount = 'logger';
$loggerPassword = 'xxxxx';
connections.php
function guestConnection() {
try {
require 'accounts.php';
$conn = new PDO("pgsql:host=$host;dbname=$database", $guestAccount, $guestPassword);
} catch (PDOException $e) {
echo "Error : " . $e->getMessage() . "<br/>";
die();
}
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
I tried to put my require in and out of my function but nothing works. I really thought this was straightfoward but I can't find an obvious solution.
Is there any thing I'm doing wrong? Isn't this the procedure?
Regards
I think you have variables defined in global scope but you try to use it in local scope.
To confirm naming scope issue try to:
require 'accounts.php';
function guestConnection() {
global $host, $database, $guestAccount, $guestPassword;
try {
$conn = new PDO("pgsql:host=$host;dbname=$database", $guestAccount, $guestPassword);
...
This should work if your guestConnection() is in global scope not under any namespace or class.
or do move require out of try to get errors if any (file does not exist?):
function guestConnection() {
require('accounts.php');
try {
$conn = new PDO("pgsql:host=$host;dbname=$database", $guestAccount, $guestPassword);
...
I had a similar problem on my CentOS server. Try using the full absolute path (server conf might not be configured to allow calling of same-level files this way):
require_once $_SERVER['DOCUMENT_ROOT']. '/path/to/accounts.php';
Note:
you can use require and it should still work. I just think require_once is safer (in terms of not accidentally duping code).

How to execute more than one statement in try command

I am trying to establish a PDO connection using the try block and then printing a success message after the connection gets established. My code is as follows:
index.php :
<?php
require_once './vendor/autoload.php';
$con = new \app\db\Connect();
if ($con)
{
echo 'connection successful.';
}
connect.php
<?php
namespace app\db;
class Connect
{
public function __construct()
{
// set the connection variables
$host = 'localhost';
$dbname = 'pdoposts';
$username = 'root';
$password = 'root';
// create the DSN
$dsn = 'mysql:host=' . $host . ';dbname=' .$dbname;
// create PDO connection
try {
static $con;
$con = new PDO($dsn, $username, $password);
} catch(Exception $e) {
echo $e->getMessage();
}
return $con;
}
}
My problem is that there is no way for me to use the $concreated in the try - catch command in the index.php file. I am very sorry if this question seems very basic and elemental one, but please help me understand how it can work. Thank you very much in advance.
Accoring to the answer below:
The solution here on stackoverflow
I just added the backslash right before PDO and made it to be \PDO and it is now working properly. The link below can help understanding PHP Callbacks and how they work better:
Using namespaces: fallback to global function/constant
I should have kept in mind the namespace from which I wanted to use PDO from. In this case, using a \ would tell php to use PDO from the Global Namespace and not from the namespace from which the code runs in (app\db).

how to avoid the new PDO(...) line of code to appear too often?

There is something, as a newbie, that a I want to understand about About database connections.
I am starting off from a tutorial on PHP which has this structure:
Connect.php:
<?php
$username = "dbusername";
$password = "dbpassword";
$host = "localhost";
$dbname = "dbname";
$options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8');
try
{
$db = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options);
}
catch(PDOException $ex)
{
die("Failed to connect to the database: " . $ex->getMessage());
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
header('Content-Type: text/html; charset=utf-8');
session_start();
?>
Login.php:
<?php
require("connect.php");
// some code not important for this question,
//that handles login with a session…
?>
various_file_in_the_login_system.php:
<?php
require("connect.php");
// some code that checks if user is logged in with session_ …
// some code that does need the database connection to work
?>
All other files also contain that require("connect.php"); line. It works, but I just don’t know what these connection request to the server – I may not be using the right vocabulary -- end up doing to the server. They are superfluous if the connection is not timed out, are they not?
I found a post which talked about doing a singleton for PDO, and a post which makes me feel like never using persistent connections in my life.
Does this design causes excessive connection churning?
Perhaps servers can handle very many request for connection per second, perhaps a server has its own internal persistent connection mode, or implements connection pooling…
Or the PDO object handle the problem of asking connection too often for no reason…
PDO + Singleton : Why using it?
What are the disadvantages of using persistent connection in PDO
This is what I can recommend for your database connection:
Make a class for the connection:
class Database{
private static $link = null ;
public static function getConnection ( ) {
if (self :: $link) {
return self :: $link;
}
$dsn = "mysql:dbname=social_network;host=localhost";
$user = "user";
$password = "pass";
self :: $link = new PDO($dsn, $user, $password);
return self :: $link;
}
}
Then you can get the connection like this:
Database::getConnection();
The Singleton Pattern is hard to scale - However, I think it will probably be fine for your needs. It takes a lot of load off your database.
I don't think you will be able to avoid the multiple includes.
There is a php.ini setting for prepending a file to every script -> http://www.php.net/manual/en/ini.core.php#ini.auto-prepend-file
How about you change to
require_once('connect.php');
in all locations?
Also you should probably remove session_start() and HTTP header logic from a section of code that has to do with establishing a DB connection. This simply does not make sense there.

Closing my mySQL connection

I'm a .Net developer that have taken over a PHP project. This project has a database layer that looks like this:
<?php
class DatabaseManager {
private static $connection;
const host = "projectname.mysql.hostname.se";
const database = "databaseName";
const username = "userName";
const password = "password";
public function __construct()
{
}
public function instance($_host = null, $_username = null, $_password = null)
{
if(!self::$connection)
{
if(!$_host || !$_username || !$_password)
{
$host = self::host;
$username = self::username;
$password = self::password;
}else{
$host = $_host;
$username = $_username;
$password = $_password;
}
self::$connection = mysql_connect($host, $username, $password);
$this->setDatabase();
}
return self::$connection;
}
public function setDatabase($_database = null)
{
if(!$_database)
{
$database = self::database;
}else{
$database = $_database;
}
$connection = $this->instance();
mysql_select_db($database, $connection) or die(mysql_error());
}
} ?>
I have written a php file that uses this layer but after a while i got these mysql errors implying i didn't close my connections which i hadn't. I try to close them but know i get other weird errors like system error: 111. Very simplyfied my php file looks like this:
<?php
$return = new stdClass();
$uid = '9999999999999';
$return->{"myUid"} = $uid;
$dm = new DatabaseManager();
$dmInstance = $dm->instance();
/* MY CLICKS */
$sql = sprintf("SELECT count(*) as myClicks FROM clicks2011, users2011 WHERE clicks2011.uid = users2011.uid AND users2011.uid = %s AND DATEDIFF(DATE(at), '%s') = 0 AND exclude = 0", mysql_real_escape_string($uid), mysql_real_escape_string($selectedDay));
$result = mysql_query($sql, $dmInstance) or die (mysql_error());
$dbResult = mysql_fetch_row($result);
$return->{"myClicks"} = $dbResult[0];
mysql_close($dmInstance);
echo json_encode($return); ?>
Okay, I'm going to post this as an answer because I think one (possibly both) of these things will help you.
First: You don't need to manually close your MySQL connections. Unless you have set them up so that they persist, they will close automatically. I would avoid doing that unless you determine that every other problem is NOT the solution.
In addition, I would switch to using prepared statements. It's more secure, and pretty future-proof. I prefer PHP's PDO over mysqli, but that's up to you.
If you'd like to look over an example of a simple PDO object to take the many lines out of creating prepared statements and connections and getting results, you can look at my homebrew solution.
Second: "System Error 111" is a MySQL error. From what I've read, it appears that this error typically occurs when you are using PHP and MySQL on the same server, but telling PHP to connect to MySQL via an IP address. Switch your $host variable to 'localhost'. It is likely that this will solve that error.
The problem here is you're calling mysql_close and not specifying a valid mysql connection resource object. You're, instead, trying to close an instance of the DatabaseManager object.
You'll probably want to run mysql_close(DatabaseManager::connection); which is where the DatabaseManager is storing the resource object.
Additionally, I'd personally recommend you learn PDO or use the mysqli drivers. In future releases of PHP the built in mysql functions will be moved into E_DEPRECATED
Try implement __destrcut
public function __destruct()
{
mysql_close(self::$connection)
}
Then simply use unset($dm);

Connecting to two databases

I have an application in which I want to authenticate a user from a first database & manage other activities from another database.
I have created two classes. An object of the classes is defined in a file:
$objdb1=new db1(),$objdb2=new db2();
But when I try to call $objdb1->fn(). It searches from the $objdb2 & is showing table1 doesnot exists?
My first file database.php
class database
{
private $hostname;
private $database;
private $username;
private $password;
private $dblinkid;
function __construct()
{
if($_SERVER['SERVER_NAME'] == 'localhost')
{
$this->hostname = "localhost";
$this->database = "aaaa";
$this->username = "xxx";
$this->password = "";
}
else
{
$this->hostname = "localhost";
$this->database = "xxx";
$this->username = "xxx";
$this->password = "xxx";
}
$this->dblinkid = $this->connect();
}
protected function connect()
{
$linkid = mysql_connect($this->hostname, $this->username, $this->password) or die("Could not Connect ".mysql_errno($linkid));
mysql_select_db($this->database, $linkid) or die("Could not select database ".mysql_errno($linkid)) ;
return $linkid;
}
Similarly second file
class database2
{
private $vhostname;
private $vdatabase;
private $vusername;
private $vpassword;
private $vdblinkid;
function __construct()
{
if($_SERVER['SERVER_NAME'] == 'localhost')
{
$this->vhostname = "xxx";
$this->vdatabase = "bbbb";
$this->vusername = "xxx";
$this->vpassword = "";
}
else
{
$this->vhostname = "localhost";
$this->vdatabase = "xxxx";
$this->vusername = "xxxx";
$this->vpassword = "xxxx";
}
$this->vdblinkid = $this->vconnect();
}
protected function vconnect()
{
$vlinkid = mysql_connect($this->vhostname, $this->vusername, $this->vpassword) or die("Could not Connect ".mysql_errno($vlinkid));
mysql_select_db($this->vdatabase, $vlinkid) or die("Could not select database ".mysql_errno($vlinkid)) ;
return $vlinkid;
}
Third file
$objdb1 = new database();
$objdb2 = new database2();
Can you help me on this?
Regards,
Pankaj
Without knowing your classes, it is difficult to help. If you are using PDO, I can guarantee you that you can create multiple instances connected to different databases without any problem. If you are using the mysql_ family of functions you probably just forgot to set the link_identifier parameter (see here).
However, having a class db1 and a class db2 sounds like a code smell to me. You probably want to have two instances of the same class with different attributes.
Each time you call mysql_connect() or the equivalent mysqli functions, if a connection already exists using those same credentials it gets reused - so anything you do to modify the state of the connection, including changing database, charsets, or other mysql session variables affects "both" connections.
Since you are using the mysql_connect() function you have the option to force a new connection each time but this is not supported on all the extensions (IIRC mysqli and PDO don't alow for this).
However IMHO this is the wrong way to solve the problem. It just becomes messy trying to keep track of what's connected where.
The right way would be to specify the database in every query:
SELECT stuff FROM aaaa.first f, aaaa.second s
WHERE f.something=s.something;
Most likely your class does not pass the appropriate connection resource to the database functions. The second argument to e.g. mysql_query() is the connection resource. Simply store this resource in an instance variable on connection, and use it every time you do something with the database.
Your problem may be in checking if the SERVER_NAME is "localhost". Seems like you may be using the same connection strings in both classes. What is $_SERVER['SERVER_NAME'] resolving to?
You're looking for the fourth parameter of mysql_connect(), which states that it shouldn't reuse existing connections:
$dbLink1 = mysql_connect($server, $user, $pass, true);
mysql_select_db($db1, $dbLink1);
$dbLink2 = mysql_connect($server, $user, $pass, true);
mysql_select_db($db2, $dbLink2);
mysql_query("SELECT * FROM table1", $dbLink1); // <-- Will work
mysql_query("SELECT * FROM table1", $dbLink2); // <-- Will fail, because table1 doesn't exists in $db2
Passing the fourth parameter as true to mysql_connect resolves the issue.
$linkid = mysql_connect($this->hostname, $this->username, $this->password,true) or die("Could not Connect ".mysql_errno($linkid));

Categories