ساختن Breadcrumbهای دینامیک در Laravel
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 15 دقیقه

ساختن Breadcrumbهای دینامیک در Laravel

اهمیت Breadcrumbها

در گذشته، توسعه دهندگان وب بیشتر نگران توابع و اطلاعاتی که وب‌اپلیکیشن‌ها می‌توانستند تحویل دهند (backend) بودند، و زیاد به ظاهر بصری (frontend) صفحات وب اهمیت نمی‌دادند. این مسئله تقصیر آن‌ها نبود. در آن زمان فناوری مناسبی برای توسعه صفحات وب پیچیده وجود نداشت. موارد کمی هم که می‌توانستند این کار را انجام دهند، استفاده سختی داشتند.

امروزه این مسئله یک داستان کاملا متفاوت است؛ جایگاه طراحی رابط کاربری / تجربه کاربری در توسعه وب، درست به اندازه برنامه‌ریزی منطق backend که همه چیز را در حالت اجرا نگه می‌دارد، مهم است. طیف بزرگی از الگوهای طراحی قابل قبول وجود دارد که می‌تواند به یک وب‌اپلیکیشن کمک کند تا برچسب «کاربر دوست» را دریافت کند، اما حتی اشتباهات طراحی بیشتری وجود دارند که همه باید مواظب آن‌ها باشند.

مردم از این که صفحات popup ناخواسته را بر روی صفحه ببینند، متنفرند. من همینطور هستم، اما popupها تنها مواردی نیستند که می‌توانند جهت راه کاربر را تغییر دهند.

این مسئله می‌تواند یک مشکل جدی باشد، اما به هیچ وجه اجباری نیست. Breadcrumbها می‌توانند ساده‌ترین راه حل برای یک تجربه کاربری نرم برای کاربر باشند.

Breadcrumbها یک سیستم جهت‌یابی فراهم می‌کنند تا به کاربران در دانستن موقعیت فعلی خود در رابطه با دیگر صفحات بر روی یک وبسایت کمک کنند. نام Breadcrumb از داستان معروف، یعنی Hansel and Grettel گرفته شده است، زیرا تقریبا همان اتفاق می‌افتد. یک رد باقی گذاشته می‌شود تا از گم شدن جلوگیری شود، و در نتیجه تجربه کاربر ترقی داده شود. Breadcrumbها بر روی یک وبسایت، همچنین تعداد حرکاتی که یک کاربر باید برای رسیدن به یک صفحه انجام دهد را کاهش می‌دهند.

راه‌اندازی Breadcrumbها در Laravel بسیار ساده است. پکیجی وجود دارد که به اکثر منطق این قضیه رسیدگی می‌کند، و ما به نحوه استفاده از این پکیج و رسیدن به بهترین نتیجه نگاهی خواهیم داشت.

راه‌اندازی برنامه Laravel

کلیت این مقاله، توسعه دهندگان Laravel را مورد هدف قرار می‌دهد.

ما پکیج Laravel Breadcrumbs را از طریق Composer وارد خواهیم کرد، و کد مورد نظر را برای رندر کردن جهت‌یابی سرویس Breadcrumb به صورت دینامیک، بر حسب صفحه‌ای که کاربر در حال مرور است خواهیم نوشت.

کد نهایی این مقاله، در این صفحه بر روی گیت‌هاب در دسترس است.

در جهت ساده‌تر شدن کار، ما یک نمونه برنامه Laravel جدید را نصب خواهیم کرد.

laravel new breadcrumbs

یا

composer create-project --prefer-dist laravel/laravel breadcrumbs

سپس با نوشتن دستور زیر در ترمینال، پکیج Laravel Breadcrumbs را وارد می‌کنیم:

composer require davejamesmiller/laravel-breadcrumbs

ساخت HTTP routing

بیایید برخی routeهای HTML را در فایل routes/web.php بسازیم. (و همچنین نامگذاری کنیم) ما در بخش‌های بعدی، با استفاده از نام این routeها به آن‌ها اشاره خواهیم کرد.

این‌ها routeهایی هستند که ما برای این مثال نیاز خواهیم داشت:

routes/web.php:

Route::get('/',  ['as' => 'home', 'uses' => 'MainController@home']);

Route::get('/continent/{name}',  ['as' => 'continent', 'uses' => 'MainController@continent']);

Route::get('/country/{name}',  ['as' => 'country', 'uses' => 'MainController@country']);

Route::get('/city/{name}',  ['as' => 'city', 'uses' => 'MainController@city']);

در جهت نشان دادن قدرت Laravel Breadcrumbs، ما یک برنامه کوچک خواهیم ساخت که در آن مدل‌های قاره، کشور و شهر را ثبت می‌کنیم. ما همچنین خواهیم گفت که یک قاره چند کشور دارد (hasMany) و یک کشور چند شهر دارد. (hasMany)

برای ساخت این مدل‌ها، این دستورات را در ترمینال می‌نویسیم:

php artisan make:model Continent
php artisan make:model Country
php artisan make:model City

همچنین بیایید فایل مهاجرت را برای هر کدام از این مدل‌ها بسازیم:

php artisan make:migration continents
php artisan make:migration countries
php artisan make:migration cities

سپس، بیایید متدهای رابطه (relationship) را در هر کدام از این کلاس‌های مدل تعریف کنیم:

app/Continent.php:

namespace App;
use App\Country;

use Illuminate\Database\Eloquent\Model;

class Continent extends Model
{
    public function country(){
        return $this->hasMany(Country::class);
    }
}

app/Country.php:

namespace App;

use App\City;
use App\Continent;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{

    protected $guarded = [];
    public function city(){
        return $this->hasMany(City::class);
    }

    public function continent(){
        return $this->belongsTo(Continent::class);
    }
}

app/City.php:

namespace App;


use App\Country;

use Illuminate\Database\Eloquent\Model;

class City extends Model
{

    protected $guarded = [];
    public function country(){
        return $this->belongsTo(Country::class);
    }
}

دقت کنید که guarded برابر با یک آرایه خالی قرار داده شده است؛ زیرا ما می‌خواهیم وقتی که در حال نوشتن بر روی دیتابیس هستیم، نوعی اختصاص دهی بزرگ انجام دهیم. هر زمان که یک تغییر این چنینی به یک فایل مدل اعمال می‌کنید، همیشه به یاد داشته باشید که قبل از رسیدن برنامه به مرحله تولید، این تغییر را برگردانید.

حال بیایید یک کنترلر بسازیم که درخواست‌های HTTP که برنامه دریافت می‌کند را مدیریت خواهد کرد. ما این کنترلر را MainController نامگذاری کرده، و actionهای (methodهای) مختلفی به نمونه‌های compact مدل با view برگشتی تعریف خواهیم کرد. هر view، Breadcrumb(های) مخصوص خود را نمایش خواهد داد.

برای ساخت این کنترلر، این دستور را در ترمینال می‌نویسیم:

php artisan make:controller MainController

حال بیایید actionها‌ (methodها)ای که viewها را پس از مدیریت درخواست‌های HTTP بر می‌گردانند، بنویسیم.

app/Http/Controllers/MainController.php:

namespace App\Http\Controllers;
use App\Continent;
use App\Country;
use App\City;
use Illuminate\Http\Request;

class MainController extends Controller
{
    public function home(){
        return view('home');
    }

     public function continent($name){
           $continent = Continent::where('name', $name)->first();
        return view('continent', compact('continent'));
    }

     public function country($name){
           $country = Country::where('name', $name)->first();
        return view('country', compact('country'));
    }

     public function city($name){
           $city = City::where('name', $name)->first();
        return view('city', compact('city'));
    }
}

ما رابطه‌های میان مدل‌ها را include کرده‌ایم، تا بتوانیم قابلیت‌های پکیج Laravel Breadcrumbs را وقتی که به لینک کردن دینامیک و ویژگی‌های ارتباطی می‌رسد، ببینیم.

حال بیایید برخی viewها را بسازیم، که با مدل‌هایی که ساختیم برابری خواهد داشت. در شاخه resources/views، چهار فایل جدید اضافه می‌کنیم. این ۴ فایل را به این صورت نامگذاری می‌کنیم:

home.blade.php
continent.blade.php
country.blade.php
city.blade.php

نام‌های آن‌ها کاملا خلاقانه هستند. اولین مورد کد frontend را برای صفحه خانه نگه می‌دارد، دومین مورد کد frontend را برای صفحه قاره‌ها نگه می‌دارد، سومین مورد کد frontend را برای صفحه کشورها نگه می‌دارد و چهارمین مورد کد frontend را برای صفحه شهر‌ها نگه می‌دارد.

خب، حال بیایید مقداری کد در این viewهای جدید قرار دهیم.

resources/views/home.blade.php:

<!DOCTYPE html>
<html>
<head>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

 </head>
 <body>

{{ Breadcrumbs::render('home') }}

</body>
</html>

resources/views/continent.blade.php:

<!DOCTYPE html>
<html>
<head>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

 </head>
 <body>

{{ Breadcrumbs::render('continent', $continent) }}

</body>
</html>

resources/views/country.blade.php:

<!DOCTYPE html>
<html>
<head>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

 </head>
 <body>

{{ Breadcrumbs::render('country', $country->continent, $country) }}

</body>
</html>

resources/views/city.blade.php:

<!DOCTYPE html>
<html>
<head>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

 </head>
 <body>

{{ Breadcrumbs::render('city', $city->country->continent, $city->country, $city) }}

</body>
</html>

مدل‌های Continent و Country نمایانگر یک رابطه «یک به چند» هستند. تعداد زیای شهر به یک کشور تعلق دارند، و تعداد زیادی کشور به یک قاره تعلق دارند. قاره، پدربزرگ شهر است و کشور والد آن است. ما به منطقی که رابطه‌های Laravel را تشکیل می‌دهد وارد نخواهیم شد.

در آخر، بیایید فایل‌های مهاجرت را کمی تغییر دهیم تا ساختار صحیح برای جدول در دیتابیس را تعریف کنند.

database/migrations/2017_11_02_092826_continent.php:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Continents extends Migration
{
    /**
     * مهاجرت‌ها را اجرا کن.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('continents', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * مهاجرت‌ها را برعکس کن..
     *
     * @return void
     */
    public function down()
    {
        //
    }
}

database/migrations/2017_11_02_092835_countries.php:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Countries extends Migration
{
    /**
     * مهاجرت‌ها را اجرا کن.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('countries', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('continent_id');
            $table->timestamps();
        });
    }

    /**
     * مهاجرت‌ها را برعکس کن.
     *
     * @return void
     */
    public function down()
    {
        //
    }
}

database/migrations/2017_11_02_092845_cities.php:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Cities extends Migration
{
    /**
     * مهاجرت‌ها را اجرا کن.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('country_id');
            $table->timestamps();
        });
    }

    /**
     * مهاجرت‌ها را برعکس کن.
     *
     * @return void
     */
    public function down()
    {
        //
    }
}

ما می‌توانیم مهاجرت را با استفاده از این دستور در ترمینال اجرا کنیم:

php artisan migrate

بیایید مقداری داده به دیتابیس وارد کنیم تا بتوانیم برنامه را امتحان کنیم. برای انجام این کار، سه فایل factory می‌سازیم (هر کدام برای یک مدل) و متد run فایل DatabaseSeeder.php (که از قبل موجود است) را در شاخه database/seeds بروزرسانی می‌کنیم.

برای ساخت فایل‌های factory، این دستورات را در ترمینال اجرا می‌کنیم:

php artisan make:factory ContinentFactory --model=Continent
php artisan make:factory CountryFactory --model=Country
php artisan make:factory CityFactory --model=City

دستور بالا این فایل‌ها را به ترتیب خواهد ساخت:

database/factories/ContinentFactory.php:

use Faker\Generator as Faker;

$factory->define(App\Continent::class, function (Faker $faker) {
    return [
        //
    ];
});

database/factories/CountryFactory.php:

use Faker\Generator as Faker;

$factory->define(App\Country::class, function (Faker $faker) {
    return [
        //
    ];
});

database/factories/CityFactory.php:

use Faker\Generator as Faker;

$factory->define(App\City::class, function (Faker $faker) {
    return [
        //
    ];
});

در آخر، بیایید فایل DatabaseSeeder.php را که تمام نمونه‌های تازه یک برنامه Laravel را دارد را بروزرسانی کنیم. ما از این فایل استفاده خواهیم کرد، تا سه ردیف به دیتابیس وارد کنیم. ما Africa، (از مدل قاره) South Africa (از مدل کشور) و Johannesburg (از مدل شهر) را وارد کرده، و همچنین رابطه آن‌ها را مشخص می‌کنیم:

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * نوشتن در دیتابیس را اجرا کن.
     *
     * @return void
     */
    public function run()
    {

      factory(App\City::class)->create([
        'name' => 'Johannesburg',
        'country_id' => function(){

            return factory(App\Country::class)->create([
                'name' => 'South Africa',
                'continent_id' => function(){

                    return factory(App\Continent::class)->create([
                        'name' => 'Africa' ])->id;
                }
                ])->id;
        }
        ]);
    }
}

ما می‌توانیم با اجرای این دستور در ترمینال، دیتابیس را پر کنیم:

php artisan db:seed --class=DatabaseSeeder

راه‌اندازی فایل Breadcrumb

ما باید فایلی به نام breadcrumbs.php در شاخه routes بسازی. این فایل‌، هر زمان که یک breadcrumb رندر می‌شود، ارجاع خواهد شد؛ زیرا به Laravel می‌گوید که چگونه اطلاعات breadcrumb را پردازش کند. بدون این فایل، Laravel هر زمان که با یک فراخوانی به یک تابع breadcrumb در viewها مواجه شود، یک خطا را برای ما به نمایش خواهد گذاشت.

اولین تابع breadcrumb که در فایل جدید به نام breadcrumbs.php که ساختیم تعریف می‌کنیم، تابع مربوط به صفحه خانه ما خواهد بود. Breadcrumb به نام «home» هر زمان که routeای با نام «home» بازدید شود، بارگذاری خواهد شد.

routes/breadcrumbs.php:

Breadcrumbs::register('home', function ($breadcrumbs) {
     $breadcrumbs->push('Home', route('home'));
});

رندر کردن یک Breadcrumb استاتیک

در کد بالا، می‌بینیم که کلاس Breadcrumb برای فراخوانی یک متد استاتیک استفاده می‌شود. متد ثبت، یک Breadcrumb جدید با نام «home» است و یک closure را فراخوانی می‌کند. سپس این closure یک پارمتر $ breadcrumb را می‌گیرد و یک نمونه Breadcrumb جدید به همراه URL آن می‌فرستد.

$breadcrumbs->push('Home', route('home'));

«Home» در متد push، hard-code شده است و هر زمان که Breadcrumb مورد نظر بر روی view خانه، یا هر view دیگری که به Breadcrumb خانه برای نمایش یک زنجیره جهت‌یابی کامل نیاز دارد رندر شود، ظاهر خواهد شد.

route(‘home’)، یو‌آر‌اِل «home» را بر می‌گراند و لینکی خواهد بود که هر زمان Home بر روی یک view جدید رندر می‌شود، به آن ختم خواهد شد.

در آخر، در کد view خانه، Breadcrumb را به این صورت رندر می‌کنیم:

resources/views/home.blade.php:

{{ Breadcrumbs::render('home') }}

متد render نام Breadcrumbای که باید بر روی view خانه نمایش داده شود را دریافت می‌کند. در زنجیره‌های جهت‌یابی پیچیده‌تر که نوعی رابطه دیتابیس را شامل می‌شوند، تابع render هر تعداد نمونه Models که برای رندر کردن زنجیره جهت‌یابی Breadcrumb مورد نیاز هستند را به عنوان آرگومان قبول خواهد کرد.

در ادامه بیشتر راجب این مسئله صحبت خواهیم کرد.

رندر کردن یک Breadcrumb دینامیک

وقتی که یک وب‌اپلیکیشن داریم، و صفات دقیق صفحاتی که کاربرها بازدید خواهند کرد را نمی‌دانیم، چه اتفاقی می‌افتد؟ برای مثال، وبسایتی داریم که اطلاعاتی درباره قاره‌ها، کشورها و شهرها را نمایش می‌دهد. ما همیشه نمی‌توانیم به فایل routes/breadcrumbs.php رفته و نام هر قاره‌، کشور یا شهری را  hard-codeکنیم؛ زیرا هیچ وقت نمی‌دانیم که کاربر کدام مورد را بازدید خواهد کرد.

در جهت ممکن کردن رندر کردن breadcrumb به صورت دینامیک و realtime، می‌توانیم فایل routes/breadcrumbs.php را به گونه‌ای بنویسیم که به صورت موثر با مدل‌های دینامیک در برنامه ما کار کند و همینطور که کاربرها لایه‌های عمیق‌تری از وب‌اپلیکیشن ما را مرور می‌کنند، زنجیره جهت‌یابی breadcrumb را بسازیم.

همانطور که پیش‌تر گفتیم، مدل قاره چندین کشور را دارد (continent model hasMany countries) و مدل کشور چندین شهر را دارد. (country model hasMany cities) با دانستن این مسئله، باید بتوانیم یک نمایش breadcrumb به دست بیاوریم که درست به مانند این باشد که ما Johannesburg از آفریقا را بازدید می‌کنیم، که در آن South Africa به Africa تعلق دارد.

برای ساخت زنجیره جهت‌یابی Breadcrumbها با استفاده از رابطه‌های Model به مانند آنچه در بالا می‌بینیم، با نوشتن کد مربوط به ثبت breadcrumb با نام continent شروع می‌کنیم:

routes/breadcrumbs.php:

Breadcrumbs::register('continent', function ($breadcrumbs, $continent) {
    $breadcrumbs->parent('home');
    $breadcrumbs->push($continent->name, route('continent', ['name' => $continent->name]));
});

ما یک breadcrumb به نام continent ثبت کرده، و یک closure را منتقل می‌کنیم. این closure یک پارامتر جدید به نام $continent (که توسط view مورد نظر به صورت realtime عرضه می‌شود) را دریافت می‌کند. سپس، breadcrumb به نام home به والد خود اختصاص داده می‌شود و در آخر، نام و یو‌آر‌اِل breadcrumb با نام $continent وارد می‌شود.

برای رندر کردن breadcrumb در کد view قاره، این قطعه کد را می‌نویسیم:

resources/views/continent.blade.php:

{{ Breadcrumbs::render('continent', $continent) }}

متد render نام breadcrumb را به عنوان آرگومان اول دریافت می‌کند. این متد همچنین آرگومان $continent را دریافت می‌کند که در resolve کردن نمونه مدل قاره به ویژگی پایه‌اش مانند نام و URL کمک خواهد کرد.

بر روی دستگاه خود و در آدرس http://127.0.0.1:8000/continent/africa، صفحه قاره Africa را به همراه نمایش breadcrumb زیر، دریافت خواهید کرد:

رندر کردن یک زنجیره جهت‌یابی کامل

ما به کد مربوط به رندر کردن breadcrumbهای تکی نگاه داشته‌ایم. حال بیایید به کد مربوط به رندر کردن یک زنجیره breadcrumb کامل نگاهی داشته باشیم. ما یک صفحه شهر که به یک صفحه کشور مرتبط است،‌ یک صفحه قاره و در نهایت یک صفحه خانه را رندر خواهیم کرد.

این کد نهایی برای فایل routes/breadcrumbs.php است. (البته برای این مقاله. این کد می‌تواند بر حسب هدفی که دارید، بزرگ‌تر شود.)

routes/breadcrumbs.php:

Breadcrumbs::register('home', function ($breadcrumbs) {
     $breadcrumbs->push('Home', route('home'));
});

Breadcrumbs::register('continent', function ($breadcrumbs, $continent) {
    $breadcrumbs->parent('home');
    $breadcrumbs->push($continent->name, route('continent', ['name' => $continent->name]));
});

Breadcrumbs::register('country', function ($breadcrumbs, $continent, $country) {
    $breadcrumbs->parent('continent', $continent);
    $breadcrumbs->push($country->name, route('country', ['name' => $country->name]));
});

Breadcrumbs::register('city', function ($breadcrumbs, $continent, $country, $city) {
    $breadcrumbs->parent('country', $continent, $country);
    $breadcrumbs->push($city->name, route('city', ['name' => $city->name]));
});

ما دو breadcrumb دیگر را اضافه کرده‌ایم: country و city. ما به این breadcrumbها نیاز خواهیم داشت، تا تمام breadcrumbها بتوانند هر زمان که یک view برای یک زنجیره جهت‌یابی فراخوانی می‌کند که شامل بیش از یک breadcrumb است، به صورت موثر با یکدیگر ارتباط داشته باشند.

متدهای مربوط به ثبت و وارد کردن breadcrumbهای country و city، مشابه متدهای مربوط به continent هستند؛ به همین دلیل به جزئیات آن‌ها وارد نخواهیم شد.

برای رندر کردن breadcrumb در کد view برای صفحه city، به سادگی این قطعه کد را از موقعیت مورد نظر وارد می‌کنیم:

resources/views/city.blade.php:

{{ Breadcrumbs::render('city', $city->country->continent, $city->country, $city) }}

متد render در اینجا نام breadcrumb را به عنوان آرگومان اول دریافت می‌کند. سپس، یک آرگومان به نام $city->country->continent را دریافت می‌کند که به یک نمونه مدل continent ارزیابی خواهد شد. این متد همچنین یک آرگومان به نام $city->country دریافت می‌کند که کشوری است که شهر به آن تعلق دارد (belongsTo) و در نهایت، یک نمونه $city.

در اینجا کار ما به پایان می‌رسد. ما جهت‌یابی breadcrumb عملکردی و دینامیک خود را در چند خط کد به طور کامل ساخته‌ایم.

نتیجه گیری

در دنیایی که کاربران سرویس‌های اینترنت به دنبال راحت‌ترین محصولات هستند، اضافه کردن breadcrumb به وبسایت‌ها، یک کار کاملا صحیح از طرف توسعه دهندگان است. این مقاله این مسئله را به ما آموخته است. با تنها چند خط کد، می‌توانیم یک سیستم جهت‌یابی کاملا عملکردی بسازیم. کارهای بسیار بیشتری هم هستند که می‌توانند با استفاده از پکیج Laravel Breadcrumb انجام شوند، (برای مثال می‌توانیم استایل و طرح را طبق میل خود تحت تاثیر قرار دهیم) پس بروید و حداکثر قدرت آن را آزمایش کنید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
2.5 از 2 رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

عرفان کاکایی