Quantcast
Channel: Live News for Yii Framework
Viewing all articles
Browse latest Browse all 3375

[Wiki] Yii for beginners 2

$
0
0

Intro (Part 2)

Hi :-) Is that you again? Welcome !! :-)

This is second article with my tutorial. I had to add it because Wiki article has limited length and as I was extending it's text, older paragraphs were disappearing. So here I am starting from chapter 6.

Previous article can be found here: Yii for beginners 1.

6. Your Own SQL

Sometimes it is much easier to create your own SQL query. I measured difference between ActiveRecord query and pure SQL. Pure SQL was 2x faster. Usefull mainly in ajax. So here is a small code that performs your SQL query and returns an array with results:

public static function sqlQuery($sql)
    {
        // $sql can be for example: 
        // "SELECT * FROM firstTable join secondTable ON ... GROUP BY ... "
        $connection = Yii::app()->db;
        $command = $connection->createCommand($sql);
        $results = $command->queryAll();
        return $results;
    }

Disadvantage is that you cannot use methods from ActiveRecord. For example validation etc. But it is faster and if you just read from DB, you can try it.

7. THEMES

You may want to skin your web and allow users (or yourself) to change the skins on demand. It’s very easy. In default state (in automatically created demo project) no theme is set or used. Is used some default appearance. This was the first thing that I did not understand To turn a theme on, you have to specify it in file “protected/config/main.php” by adding the 'theme' line:

return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'Hello World !!',
            'theme'=>'theme1',
…
 
);

It means that this variable is readable using Yii::app()->theme->getName(). You can set it programmatically in an ACTION (not in view!!) like this:

Yii::app()->theme = 'theme1'; // = each user can have (select) different theme

If theme is not set (using config file, or programmatically) then method getName() will throw error! Yii::app()->theme->baseUrl will contain your theme path = you can use it in layout file to address your css files, images etc. To use your theme1, you will have to create a new subfolder in “themes” folder and name it “theme1”. The “themes” folder is placed in the root of your project. In folder “theme1” will be the same structure as in “protected” folder. Create there views and CSS folders. If a theme is set, views and controllers will be taken from your theme folder. If Yii do not find them, tries to look for them in ordinary “views” and “controllers” folders. But controllers and actions can be the same for all themes. There does not have to be a reason to change them. On the other hand, CSS files will be probably different, so create CSS folder in folder of your new theme and put new CSS files in it.

... to be continued ...

8. Secured (secret) file download

Sometimes you want to offer a file to only a LOGGED IN user but don't want him to see it's path. Nobody has to know that you store files in folder: myweb.com/files. Usually links on webs look like this:

<a href="myweb.com/files/my.pdf">download</a>

But if particular files in this folder are for only particular users, there is danger, that some "smart" user will download files that are not for him, because he knows where your data-storage is.

But how to offer a file for download and not to tell users where files are stored? You can't link particular files. You have to "stream" it to user.

It is also good to store file names in DB and save files to your storage only with their IDs. Like this:

myweb.com/files/1.pdf
myweb.com/files/2.doc

etc. IDs are IDs from DB.

At the end, your download links will look like this:

myweb.com/storage/download/123/how-to-do-something.pdf

or rewritten like this:

myweb.com/controller/action/GET-ParamName/GET-ParamValue

I think now it's obvious what I want to show.

Your download links won't point to files, but to a specialised action that sends file to user. In this action you can filter users who are allowed to download your file. You can also use accessControll:

http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#access-control-filter

Finally download links look like this:

<a href="myweb.com/storage/download/123/how-to-do-something.pdf">download</a>

The action is in Controller "storage" and is called actionDownload(). It processes GET parameter like this:

$error = false;
 
// no GET parameters           
if (sizeof($_GET)<=0)
{
  $error=true;
}
 
$id = 0;
 
// we take just the first (and the only) GET parameter           
foreach ($_GET as $id_=>$title_)            
{
  $id = $id_;
  break;
}
 
if (empty($id))
{
  $error = true;
}
 
if (!is_numeric($id))
{
  $error = true;
}
 
if (strval(intval($id)) <> strval($id)) 
{
// this is a simple test that checks whether a string represents an integer
  $error = true;
} 
 
if ($error)
{
  $this->redirect( ... );
}
 
// now we know that incomming GET parameter was integer
// you may have noticed, that it's value was lost:
// myweb.com/storage/download/123/how-to-do-something.pdf
// $id = 123.

Now you just have to have a look into DB, find filename and path. When this is done, you just write this:

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($fileName));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($fileName));
ob_clean();
flush();
readfile($fileName);
exit;

File is downloaded and user doesn't know from where :-)

Instead of readfile() Yii offers CHttpRequest->sendFile(). But it is goot just for small files. Readfile() is better.

And do not forget to put .htaccess file to your storage-folder with this content:

SetHandler enginge_off
deny from all

First line banns to run PHP in this folder, second line denies direct access via browser.

9. Modules (not Models)

Module is a nested Yii project. It's good when you need to have longer path. Not only cars.com/buy/trailer (project/controller/action). But for example: cars.com/forsuppliers/howtosell/cars (project/module/controller/action). Module can have it's own controllers, actions, models.. But it can use models of parent project.

When I tried to run my first module I read the Yii manual at site:

http://www.yiiframework.com/doc/guide/1.1/en/basics.module

But (of course) didn't succeed. Yii manuals just don't describe everything that is important. They are just an overview for people, who understand it. So I had to investigate by my self.

It is simple:

  • You have your original Yii project (project A)

  • If you want to create a new module, create a brand new project using command line. (project B)

  • In project A go to folder "protected" and create there folder "modules". In it create nested folder that will have name of your new module. Lets say "forsuppliers".

  • In your project B go to folder "protected" and copy at least folders: components, controllers, views.

  • Paste them to your "forsuppliers" folder.

  • To "forsuppliers" folder add PHP file with name "ForsuppliersModule.php"

  • To this file enter following text:

<?php
class ForsuppliersModule extends CWebModule
{
// Following will be the default controller of your new module
// Yii manual doesn't mention this so modules won't work for you without further research.
// If you didn't write this line, the default controller would be "DefaultController" or "Default"
// http://www.tipstank.com/2010/11/19/change-the-default-controller-of-a-module-in-yii/
  public $defaultController = 'Site';
}
?>
  • In your project A (the parrent project) open file protected/config/main.php a find section 'modules'. Here you have to add your new module like this:
'modules'=>array(
          'forsuppliers'=>array()
),
  • Now you can use the same models like in the parent project and your url is longer: parrentProject/moduleName/controller/action

10. Translating core messages

I again found one thing that wasn't mentioned anywhere ...

If you want to set language there is one problem that I met. Yii does not remember variable Yii::app()->language! You have to set it in constructor of your basic Controller everytime again!

How?

Go to protected/components/Controller.php and make sure there is constructor with cca following content:

public function __construct($id, $module = null) 
{
  parent::__construct($id, $module);
 
  // testing if in URL was specified language and saving it to session varibale
  if (isset($_GET["lang"]))
  {
    Yii::app()->session["lang"] = $_GET["lang"];    
  }
 
  // filling variable $lang based on variable in session
  $lang = 'en_us';
 
  if (isset(Yii::app()->session['lang'])
  {
    $lang = Yii::app()->session['lang'];
  }
 
  // the most important thing - setting the language
  Yii::app()->setLanguage($lang);
 
}

In protected/config/main.php you need to add your source (basic) language. I recommend English.

'sourceLanguage' => 'en_us',

11. Security

There is one thing I would like to show you. It is very simple, but I didn't realize this security error for quite a long time.

Imagine that you are creating an administration for a forum. If a user loges in, he can edit his posts. Not posts of other people. First, he's presented with list of his posts. You surely filter them by "id_user" that is stored in session. If user clicks an item of this list, he will see details of this post and he can change it. This is done using hidden form field with id_post. But I didn’t know that it was so easy to change the value of this hidden field.

To filter posts by id_user I recommend creating a scope in Post model that will return only posts of logged user. Scope can work with the session variable id_user. This scope can be named getCurrentUsersPosts. Filtering than looks like this:

$posts = Post::model()->getCurrentUsersPosts()->findAll();
foreach ($posts as $post)
{
//
echo CHtml::activeHiddenField($post,’id_post);
}

By clicking one record, it’s ID is send to server and it shows the detail of it. It works like this:

$post = Post::model()->findByPk($_POST[id_user]);

But what if somebody changed the hidden field? Than user will see detail of post that doesn’t belong to him and he will be able to change it.

Solution? … Simple.

Just add here the scope:

$post = Post::model()‐>getCurrentUsersPosts()‐>findByPk($_POST[id_user]);

Difference? Edited post will be searched only among allowed records. If user changes the hidden field and the desired post is not his, he will receive error ($post will be empty) and you know exactly what he did.

12. Recommended software and sites

Web creation

Web page

Sites

Things to think about

  • SEO optimization of your web

  • Security of your web


Viewing all articles
Browse latest Browse all 3375