ترفندهای بهینه سازی کد در فریمورک Yii

[font="Tahoma"][rtl]توی این تاپیک میخوایم هر تکنیکی که بنظرمون میاد برای بهینگی کد توی فریمورک Yii مناسبه مطرح کنید. حتماً میدونید که بخاطر ساختار داخلی فریمورک و معماریهای بکار رفته در داخلش، یکسری جاها اصول بهینگی توی کدنویسی فریمورک Yii با خود زبان PHP خام فرق میکنه. تجربیاتتون رو با دوستانتون به اشتراک بگذارین.[/rtl][/font]

[font="Tahoma"][rtl]اولین مورد رو خودم میگذارم تا استارت بخوره این تاپیک. خیلیها توی یک پروژه برای اینکه فرضاً یک کاری رو روی تمام رکوردهای یک جدول انجام بدیم، از این روش مرسوم در خود PHP استفاده میکنیم:[/rtl][/font]


foreach(Posts::model()->findAll('confirmed=1') as $post) {

    // ...

}

[font="Tahoma"][rtl]خوب حالا ایرادش چیه؟ اینه که در ابتدای حلقه تمام رکوردهای جدول Posts بصورت شئ با تمام attributeها و… میاد توی حافظه و فرض کنید توی یک هاست اشتراکی با محدودیت حافظه میخواین 10 هزار رکورد که هرکدوم 10 تا فیلد دارن و فرضاً فیلد body برخی از اونها حدود 3-2 کیلوبایته پردازش بشه. اینجا نه تنها به سرور فشار میاد بلکه ممکنه حتی سایت شما رو Suspend کنن یا خطای Maximum memory allocated و… بگیرین. تازه الان ما از with و… استفاده نکردیم و هیچ Join و… در کار نیست وگرنه احتمالاً مسئول هاست شخصاً میومد کتک هم میزد ما رو!!!

حالا راه حل چیه؟[/rtl][/font]


$id = 0;

while($post = Posts::model()->find('id>:id AND confirmed=1', array(':id'=>$id))) {

    $id = $post->id;

    // ...

}

[font="Tahoma"][rtl]چیزی که مسلمه اینه که رکوردی با id صفر نداریم و از 1 شماره میخوره رکوردها. خوب ما اول میایم شرط میگذاریم id بزرگتر از صفر رو پیدا کنه (بخاطر find بجای findAll فقط یک رکورد استخراج میشه) و بعد از استخراج، id اون رو بعنوان شرط استخراج رکورد بعدی درنظر میگیریم. اینطوری وقتی دیگه رکوردی نباشه، از حلقه میایم بیرون ولی هربار فقط یک رکورد توی حافظه است.

امیدوارم به دردتون بخوره.


نکته مهم: این تکنیک وقتی مفیده که مصرف حافظه براتون مهمتر از سرعت باشه چون سرعتش تاحدودی کمتر از استخراج یکجای اطلاعات با یک کوئری هست ولی خوب بعضی وقتها بخصوص توی هاستهای اشتراکی حافظه خیلی کمی در اختیارتون قرار میگیره و حجم اطلاعات شما هم بالاست. اینجاست که این تکنیک به دادتون میرسه.[/rtl][/font]

[font="Tahoma"][rtl]حتماً تا حالا براتون پیش اومده که فرضاً بخواین فقط ایمیل کاربرانی که فعال شدن رو داشته باشین. راه حلی که اکثر افراد میرن اینه:[/rtl][/font]


$emails = array();

foreach(Users::model()->findAll('active=1') as $user) {

    $emails[] = $user->email;

}

[font="Tahoma"][rtl]حالا اگه فیلدهایی که میخواین استخراج بشه بیشتر بود چی؟ قطعاً دردسرتون بیشتر میشه و تازه این روش مثل مطلب شماره 2 همین تاپیک، مشکل مصرف زیاد حافظه رو داره. اما روشی که من بعنوان جایگزین میخوام معرفی کنم چیه؟

توی پوشه protected/components این کد رو به اسم ActiveRecord.php ذخیره کنید:[/rtl][/font]


class ActiveRecord extends CActiveRecord

{

    public function findColumn($columns, $condition = '', $params = null)

    {

        if($condition instanceOf CDbCriteria) {

            $criteria = $condition;

        }

        else {

            $criteria = new CDbCriteria;

            if(is_array($condition)) {

                foreach($condition as $k => $v) {

                    $criteria->{$k} = $v;

                }

            }

            else {

                $criteria->condition = $condition;

                $criteria->params = $params;

            }

            $criteria->select = $columns;

        }

        $data = array();

        $cols = array_map('trim', explode(',', $columns));

        foreach($this->findAll($criteria) as $model) {

            $row = array();

            foreach($cols as $col) {

                $row[$col] = $model->attributes[$col];

            }

            $data[] = $row;

        }

        return $data;

    }

}

[font="Tahoma"][rtl]حالا کافیه هر مدلی که میخواین قابلیت جدید findColumn رو داشته باشه، بجای CActiveRecord خود فریمورک از ActiveRecord ما مشتق بشه. برای مثال:[/rtl][/font]


class Users extends ActiveRecord { ... }

[font="Tahoma"][rtl]و حالا چطور از این متد استفاده میکنیم؟ خیلی ساده است:[/rtl][/font]


print_r(Users::model()->findColumn('email', 'active=1');

[font="Tahoma"][rtl]میتونید چند ستون رو هم داشته باشین:[/rtl][/font]


print_r(Users::model()->findColumn('username,password', 'active=:active', array(':active'=>1)));

[font="Tahoma"][rtl]تازه میتونید از تمام قابلیتهای Criteria هم استفاده کنید:[/rtl][/font]


$criteria = new CDbCriteria;

$criteria->addSearchCondition('name', 'ali');

foreach(Users::model()->findColumn('email', $criteria) as $user) {

    echo '<p>' . $user['email'] . '</p>' . PHP_EOL;

}

[font="Tahoma"][rtl]الان این دستورات به راحتی به شما آدرس ایمیل کاربرانی که توی فیلد name اونها کلمه ali ذکر شده رو نشون میده.[/rtl][/font]

[right][rtl][font="Tahoma"]

سلام

ایجاد چنین تاپیکی ایده خوبی هست. ممنون بابتش

در مورد این روش خاص، چرا اینقدر پیچدگی؟! خب میشه خیلی راحت شروط رو در یک criteria تعریف کرد و به findAll پاس داد. این روش هم پیچیدگی رو افزایش میده هم خوانایی و انعطاف کد رو کاهش

[/font][/rtl][/right]

[font="Tahoma"][rtl][/font]

[font="Tahoma"]به نظر من بهتر زمانی که قصد select کردن داریم از createCommand استفاده کنیم تا هم تو سرعت و هم تو مصرف رم کدمون بهینه شده باشه و تنها زمانی از activerecord استفاده کنیم که نیاز به insert,update یا delete داشته باشیم[/font]

[font="Tahoma"][/rtl][/font]

[font="Tahoma"][rtl]تو این روش پیچیدگی یکبار انجام میشه و بعدش تو مدلها راحت میگیم findColumn و لازم نیست هربار یک Criteria بسازیم. ازطرفی مصرف حافظه این روش هم به همون بهینگی استفاده از Criteria هست. ازطرفی خروجی این متد الان فقط یک آرایه ساده است ولی توی findAll آرایه ای از اشیاء برگردونده میشه که کلی سربار اضافه داره درحالی که ما فقط مقادیر یک یا چند فیلد خاص رو لازم داشتیم. درنتیجه تا پایان اسکریپت حافظه کمتری مصرف میشه چون حافظه findAll توی متد findColumn با تموم شدن متد، آزاد میشه.[/rtl][/font]

[font="Tahoma"][rtl]استفاده از Command هم خوبه ولی باز هم بنظرم سادگی و رواج استفاده از ActiveRecord رو نداره.[/rtl][/font]

[font="Tahoma"][rtl]دوستان عزیز لطف کنید پستهایی که طولانی هست رو اگه نقل قول میکنید، یکی دو خط اولش رو بگذارین و بقیه رو حذف کنید تا صفحه الکی طولانی نشه.[/rtl][/font]

[right]با سلام

مرسی از دوستانی که تجربیات و دانش خودشون رو به اشتراک میذارن. این نوع تابیک ها واسه افراد کم تجربه ای مثل خودم ، خیلی میتونه مفید باشه[/right]