One codebase, separate client spaces

I’m looking to port an existing application into Yii (currently old procedural PHP, but a functional and powerful app). I’m just learning Yii for the first time, so I don’t yet know what is easy vs. hard to do in a framework. One thing that I need to be able to do is allow each “client” of my database app to have their own database, directory area for uploaded files (photos and other), colors for the CSS, and possibly even “overrides” for some of the interface text (the app is bilingual, and I want to keep only one copy of the full translation files, but allow a way for specific terms to be custom for a client if necessary). The client is identified by the subdomain, and I use suexec to prevent any sneaky clients from accessing other clients’ file area.

The way I handle these things in my current code (because I hand-wrote it, so I have all freedom) is:

  • Database: connection details in small php file in the client directory that I simply include

  • Files: custom PHP files that serve binary content with the right header - e.g. [font="Courier New"][color="#8B0000"]<img src="photo.php?id=56">[/color][/font]

  • CSS colors: [font=“Courier New”][color="#8B0000"]<link rel=“stylesheet” href=“style.php” type=“text/css”>[/color][/font], then style.php includes a colors.php file in each client’s directory and serves up the CSS

  • Translation overrides: I haven’t implemented this one yet, but if I were continuing with my old code, I’d probably replace _(“string”) with __(“string”), and write function __($text) to check for a hit in a custom translation file first and then go to the regular gettext spot if not found

The question is: Can you point me in the right direction for any important clues I need early on for doing these kind of things within Yii? And if any of these will go against the “spirit” of Yii (or “buck the system”… use your favorite metaphor), I’d like to know earlier rather than later. I’m still deciding if I’ll be able to go with Yii or if I’m too much of a maverick and should just write my own OOP PHP without a third-party framework.

I’m currently working on a web application that has different client areas (client types). I control access using ‘Yii::app()->user->auth_id’ and ‘Yii::app()->user->user_perm’ which I register on login, along with a whole bunch of other stuff specific to the user.

Once a user is logged in, their template is served from /themes/ (standard to Yii), along with CSS, any custom images etc. Regular users see the main template rather than using themes.

They have their own file areas, controlled by auth.

I use a shared database, however its easy enough to use multiple databases with Yii.

I have translation files specific to user type (3 languages, scaling up to 5).

Using Yii has saved me literally months of work, so its a no brainer to use it.

Thanks for the reply and encouragement. My brain still mostly thinks procedurally, but I’m determined to scale the learning curve of OOP, MVC, and probably Yii, in order to save me maintenance time in the long run.

In your case your custom stuff is per user in a single database, so the differentiation happens after login. I might do some per-user stuff down the road, but at the moment my concern is something that would need to happen earlier in the process, before even connecting to the database to authenticate. I need to designate the path to the client-specific file area based on the URL (specifically the subdomain, the difference between client1.mydomain.com and client2.mydomain.com), and in that client file area I would find the credentials for the database connection unique to that client. Getting the path is a simple formula - e.g. /base/path/client1/ vs. /base/path/client2/, so I don’t need a lookup table/file - I can just parse the subdomain. But do you know where in Yii I would put that code? I can’t seem to find any examples of that level of separation.

And a question about your user-specific translation files: Are each of them complete files, or do you have a way to have a "master" translation that is checked if a piece of text is not found in the user-specific file?

[By the way, what you called "regular users" will not exist in this case - this application is completely limited to authenticated users, with no public face other than the login page.]

Sorry for the slow reply.

I have four types of users, its a music distribution application:

  1. General public

  2. DJs/Program Managers

  3. Music Labels

  4. Admins.

The general public sees only a limited part of the application, while DJs/Managers see new music releases - these two share the main template.

The members area is accessed by URL: r=member, however not sub domain

The labels’ area also is accessed by URL r=label

As is the admin (not telling!).

So, rather than using sub domains, I don’t see why you cannot just use simple access control through a controller for the particular section as I do.

I do use other controllers, however all of these are through Ajax only. When a member enters the members’ area, there are no further full page refreshes at all.

For translations, I just have a single file for each section/language. This is all I require and is quite simple to manage.

Hi

check this post out it give you an idea how oop and mvc works

http://www.yiiframework.com/wiki/250/yii-for-beginners/

alirz23: Thanks, but I’m past that stage of studying - the problem is that I have the urge to leap into writing things I really need, which are all much more complex than the tutorials describe how to do.

[quote name=‘Backslider’]So, rather than using sub domains, I don’t see why you cannot just use simple access control through a controller for the particular section as I do.
[/quote]
Because I need to have a completely separate database for each client. In order to have that, the database login credentials (not just the user logins) need to be stored safely in separate places outside the webroot and only accessed by a PHP include. That kind of separation is only possible with subdomains and virtual host suexec restrictions in the web server’s configuration - you can’t accomplish it purely with PHP.

What you described, I would term “user controls” - different levels of access for different users in the same database. I do have that. But there are multiple sets of users, accessing completely different data - I call those clients. Each client has its own physical database, and an admin-level user for each client has the ability to create/edit users within that client. Also, each client’s physical file storage space is separate and protected from other clients - the virtual host configuration for Apache prevents anyone from hacking access to a different client’s files. You might think of each client as a separate installation of the whole application, except that the main codebase is shared.

My question isn’t how to do the separation - the Apache stuff is already done on my server, and I know the database login credentials code would be in main.php. My question is what happens after that. I could include a lot of my existing code, but the end result might not be very “Yii-like”, so I’m asking how it would be done best in a Yii environment.

Here’s a specific example. I have a database table that stores personal information about people - one field is the ID (auto-increment number) and another is a boolean field to say whether a photo of them exists. Let’s say a given person’s ID is 478, and a photo does exist. The path to the photo file would be /path/to/client/area/photos/p478.jpg. But /path/to/client/area/ is outside the webroot, so it is currently served by a special little PHP file that gets the client name from the login session, takes a passed in person ID, and outputs the image content with the correct HTTP header for a JPG file. The HTML code as I do it now is simply:


<img src="getPhoto.php?f=p478" class="photo">

But in MVC terms, the functionality of getPhoto.php is a controller (because it is called by the browser), a model (because it’s getting the content of the file), and a view (because it’s outputing something). But it’s just s simple, little file! I could just put my getPhoto.php, as is, in the webroot directory (where index.php is), but that would be totally non-Yii-like. Even if I consider it a helper class, from what I can tell, strict MVC would dictate that I’d be calling it with something like “client.domain.com/index.php/helpers/getphoto/p478” (and that’s assuming SEO - otherwise it would be “client.domain.com/index.php?controller=helpers&action=getphoto&filename=p478”). I would need a controller called HelpersController.php that calls a model that constructs an object and puts the image content in it, then a view that sends it to the browser. That seems a bit excessive, when currently it’s just a little independent file that serves up photos and is called directly by the browser. How would one of you approach that?

A similar scenario would be displaying images stored in BLOB fields in DB (for example if access to images underlies authorization this is a far better solution than storing in filesystem).

A possible approach of displaying these images can be realized with only one(!) file - the controller:




class ProductImageController extends Controller

{


	/**

	 * @return array action filters

	 */

	public function filters()

	{

		return array(

			'accessControl', // perform access control for CRUD operations

		);

	}


	/**

	 * Specifies the access control rules.

	 * This method is used by the 'accessControl' filter.

	 * @return array access control rules

	 */

	public function accessRules()

	{

		return array(

			array('allow',

				'actions'=>array('display'),

				'users'=>array('@'),

			),

		);

	}


	/**

	 * Displays a particular model.

	 * @param integer $id the ID of the model to be displayed

	 */

	public function actionDisplay($id)

	{

		

		$model=$this->loadModel($id);

                

                #print var_dump($model->media->mime); die();

                

		

                header('Pragma: public');

                header('Expires: 0');

                header('Cache-Control: must-revalidate, post-check=0, pre-check=0');

                header('Content-Transfer-Encoding: binary');

                header('Content-length: '.$model->media->filesize);

                header('Content-Type: '.$model->media->mime);

                header('Content-Disposition: attachment; filename='.$model->media->name);

 

                echo $model->media->mediaData->attributes['data'];

                exit();

		

		

		

	}

....

}



(This example uses a meta table and a separate table for binary (image) data, therefore the relation “media” und “mediaData” - don’t get confused by that)

Based on: Saving files to a blob field in the database

I think this solutions is “Yii-like”, as you call it. If I’m wrong corrections welcome :wink: !

EDIT:

And this controller is called by the short url

Thanks for the reply.

I agree that the scenarios are similar for purposes of discussing how to structure things in Yii. But as an aside comment, I beg to differ with the statement, “this is a far better solution than storing in filesystem,” if performance is a consideration. In my case, the photos are only one example of a number of things that need to be in the filesystem but separated by client, so I still need virtual hosts. Therefore, there is no reason to put images in the database, where they will be significantly slower to access and can’t be cached by the browser. But that’s beside the point - the discussion here is about how to do the PHP, not where to store the data.

It looks like two files to me - the code shown is only the controller, but “$model=$this->loadModel($id);” appears to refer to a model. But the interesting part to me is that the tutorial did not use a view - I assumed that MVC purists would say that the header() calls and echo statement belong in a view, not the controller. Is that not true? Or is it just that this case is simple enough that it’s okay to do the output directly in the controller?