مبحث Restful Api، همواره یکی از مباحث مورد علاقهی توسعه دهندگان فریمورکهای مدرن جاوا اسکریپت و همچنین اپلیکیشنهای موبایل بوده است. Rest مخفف Representational State Transfer و در واقع، یک معماری برای تبادل اطلاعات از طریق شبکه و به وسیلهی وبسرویس است که اولین بار توسط Roy Fielding در سال 2000 میلادی معرفی شد. اصطلاح Restful هم به طور خلاصه، به وبسرویسهایی که از این معماری استفاده میکنند، اطلاق میشود.
پکیج Passport در لاراول یکی از پکیجهای رسمی لاراول است که اعتبارسنجی وبسرویسها را با استفاده از معماری Rest، برای توسعهدهندگان لاراول آسان میکند. این پکیج با استفاده از پروتکل OAuth2 و همینطور الگوریتم JWT، ایجاد توکنهای دسترسی شخصی یا Personal Access Tokenها را برای اعتبارسنجی کاربران لاگین شده ممکن میسازد. این توکن منحصربهفرد، با هر درخواست کاربر، به سمت سرور ارسال میشود و دسترسی به مسیرهای حفاظت شده را برای کاربر ممکن میسازد.
در این مقاله، قصد داریم تا شما را به طور عملی، با نحوهی ایجاد توکنهای دسترسی شخصی و نحوهی ذخیرهسازی و استفاده از آن به وسیلهی پکیج Passport در لاراول آشنا کنیم؛ تا بتوانید اعتبارسنجی وب سرویسهای اپلیکیشن خود را به آسانترین شکل، با استفاده از ویژگیهای جذاب این پکیج انجام دهید. پس تا انتهای این مقاله با آموزش استفاده از پکیج Passport در لاراول، با ما همراه باشید .
نیازمندیهای اولیه
برای شروع آموزش لاراول پاسپورت، به دانش کافی در زمینهی لاراول نیاز دارید.
نصب لاراول
به پوشهی دلخواه خود بروید و دستور زیر را برای نصب لاراول اجرا کنید:
composer create-project --prefer-dist laravel/laravel blog
با دستور زیر میتوانید پروژه را اجرا کنید:
php artisan serve
پس از اجرای این دستور، پروژه بر روی پورت 8000 لوکال هاست در دسترس است. با وارد کردن آدرس https://localhost:8000 در نوار آدرس مرورگر، با این صفحه مواجه میشوید.
راهاندازی دیتابیس
اکنون که لاراول را نصب کردهاید، قدم بعدی، برقراری ارتباط با دیتابیس است. ابتدا یک دیتابیس دلخواه را ایجاد کنید و سپس مقادیر متغیرهای زیر را در فایل env. پروژه بروز کنید.
اکنون دیتابیس آماده است. در قدم بعدی باید پس از نصب پکیج، جداول موردنظر خود را ایجاد کنیم.
نصب و راهاندازی پکیج
با اجرای دستور زیر در مسیر پروژه، پکیج Passport را نصب کنید:
composer require laravel/passport
ایجاد جداول
پس از آن که نصب پکیج به پایان رسید، با اجرای دستور زیر، جداولی که برای کار با Passport در لاراول نیاز است، به منظور ذخیرهی توکنها، کلاینتها و... را ایجاد کنید. فایل Migration مربوط به این جداول، در محتوای پکیج، موجود در پوشهی vendor قرار گرفتهاند.
Php artisan migrate
ایجاد کلیدهای رمزنگاری
پس از اجرای دستور فوق، به منظور ایجاد کلیدهای رمزنگاری توکنها، دستور زیر را اجرا کنید.
php artisan passport:install
HasApiTokens
سپس به فایل User.php در مسیر app/Models مراجعه کرده و Trait با نام HasApiTokens را به مدل مربوطه اضافه کنید.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasFactory, Notifiable, HasApiTokens;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
یکی از فواید افزودن این Trait به مدل User، دسترسی مدل به تعدادی متد کمکی است که میتوان با استفاده از این متدها کارهایی نظیر ایجاد توکن، دسترسی به توکن ایجاد شده برای کاربر، بررسی Scope توکنها و... انجام داد. در ادامه از این متدهای کمکی استفاده خواهیم کرد.
ایجاد مسیرها
اکنون باید متد ()boot در فایل AuthServiceProvider را به شکل زیر بروز کنیم:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
افزودن خط فوق به متد ()boot، مسیرهای مورد نیاز برای ایجاد و لغو توکنها را به اپلیکیشن اضافه میکند.
Api Guard
اکنون نوبت آن رسیده که با استفاده از TokenGuard در Passport، درخواستهای Api ورودی را احراز هویت کنیم. با مراجعه به فایل auth.php در پوشهی config، درایور مربوط به Guard با نام api را به شکل زیر، برابر با مقدار passport قرار دهید.
تاریخ انقضای توکنها در پکیج Passport در لاراول، به صورت پیش فرض طولانی مدت و برابر یک سال است. ما میخواهیم این مقدار پیش فرض را برای توکنهای پروژه که از نوع توکن دسترسی شخصی هستند، به یک روز تغییر دهیم. این کار را با متد ()boot در فایل AuthServiceProvider به صورت زیر انجام میدهیم.
<?php
namespace App\Providers;
use Carbon\Carbon;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::personalAccessTokensExpireIn(Carbon::now()->addHours(24));
}
}
تا اینجا تنظیمات موردنیاز Passport را به طور کامل انجام دادهایم. اکنون وقت آن فرا رسیده است که فرآیند احراز هویت کاربران را انجام دهیم.
ایجاد کنترلرها
ابتدا با اجرای دستور زیر، کنترلر AuthController را ایجاد میکنیم. این کنترلر، وظیفهی پاسخگویی به درخواستهای مربوط به ثبت نام و ورود کاربر را بر عهده دارد.
php artisan make:controller Api/v1/AuthController
دقت کنید که کنترلر را در مسیر app/Http/Controllers/Api/v1 ایجاد کردهایم. پوشهی Api را به کنترلرهای مربوط به وبسرویسها اختصاص دادهایم. اما نکتهی اصلی، قرار دادن کنترلر در پوشهی v1 است که مربوط به نسخهی اولیهی اپلیکیشنی است که ایجاد میکنیم. با انجام این کار، در صورت نیاز به ارائهی نسخههای جدیدتر اپلیکیشن، کاربران نسخههای قدیمیتر، در اجرای اپلیکیشن با مشکلی مواجه نخواهند شد، چرا که هر نسخه، مسیرها و کنترلرهای مخصوص به خود را دارد.
ایجاد Resource
ویژگی Resource از نسخهی 5.5، به لاراول اضافه شده است. Resource لایهای است که بین مدل Eloquent و پاسخ Json خروجی قرار میگیرد و تبدیل اطلاعات مدل یا کالکشن مدلها به خروجی Json را آسان میکند. همچنین میتوانید اطلاعات مدلها را به دلخواه خود فیلتر کنید. با اجرای دستور زیر یک Resource برای مدل User ایجاد میکنیم.
php artisan make:resource v1/UserResource
میبینید که Resource را در پوشهی v1 قرار دادهایم تا بتوانیم Resourceهای نسخههای مختلف اپلیکیشن را از هم جدا کنیم. اکنون Resource ایجاد شده را به شکل زیر ویرایش کنید.
<?php
namespace App\Http\Resources\v1;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
}
میتوانید هرکدام از اطلاعات کاربر را که نیاز دارید، به شکل فوق در قالب یک آرایهی Json برگردانید. نحوهی استفاده از Resource ایجاد شده را در ادامه خواهید دید.
ایجاد متدها
اکنون متدهای موردنیاز را در کنترلر AuthController موجود در مسیر app/Http/Controllers/Api/v1، پیادهسازی میکنیم.
متد ثبت نام
<?php
namespace App\Http\Controllers\Api\v1;
use App\Http\Controllers\Controller;
use App\Http\Resources\v1\UserResource;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class AuthController extends Controller
{
public function register(Request $request)
{
$data = $request->all();
$validator = Validator::make($request->all(), [
'name' => 'required|max:55|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed'
]);
if ($validator->fails()) {
return response()->json($validator->errors()->first(), 422);
}
$data['password'] = bcrypt($request->password);
$user = User::create($data);
$accessToken = $user->createToken('UserToken')->accessToken;
return response()->json([
'user' => new UserResource($user),
'token' => $accessToken,
'token_type' => 'Bearer'
]);
}
}
همانطور که میبینید، ابتدا اطلاعات ارسالی از سوی کاربر را اعتبارسنجی میکنیم. سپس رمز عبور ارسالی کاربر را با استفاده از تابع کمکی ()bcrypt رمزنگاری میکنیم. در ادامه کاربر جدید را ایجاد کرده و با استفاده از تابع کمکی ()createToken، توکن منحصربهفرد مربوط به کاربر را ایجاد میکنیم. این تابع در واقع، یک نمونه از کلاس PersonalAccessTokenResult را ایجاد میکند و توکن ایجاد شده در خصوصیت accessToken قابل دسترس است. این تابع به عنوان پارامتر اول، یک رشته را به عنوان نام توکن میپذیرد. این رشته یک نام دلخواه است. در پارامتر دوم نیز میتوان یک آرایه را برای مشخص کردن scopeهای توکن به این تابع پاس داد که افزودن آن اختیاری است.
در ادامه نیز خروجی Json را به شکل آرایه، به سمت کلاینت ارسال میکنیم. در کلید اول این آرایه، یک نمونه از Resource را که ایجاد کردهایم، قرار داده و مدل کاربر ایجاد شده را نیز به آن پاس میدهیم. در کلید دوم توکن و در کلید سوم نوع توکن را قرار میدهیم. توکنهای ایجاد شده در این حالت، از نوع Bearer هستند که در Authorization header درخواست قرار میگیرند. توکنهای Bearer بخشی از استاندارد پروتکل 0Auth2 هستند.
متدهای ورود و خروج
پس از متد register، متد login و logout را نیز در همان کنترلر به شکل زیر تعریف میکنیم.
<?php
namespace App\Http\Controllers\Api\v1;
use App\Http\Controllers\Controller;
use App\Http\Resources\v1\UserResource;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class AuthController extends Controller
{
public function register(Request $request)
{
$data = $request->all();
$validator = Validator::make($request->all(), [
'name' => 'required|max:55|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed'
]);
if ($validator->fails()) {
return response()->json($validator->errors()->first(), 422);
}
$data['password'] = bcrypt($request->password);
$user = User::create($data);
$accessToken = $user->createToken('UserToken')->accessToken;
return response()->json([
'user' => new UserResource($user),
'token' => $accessToken,
'token_type' => 'Bearer'
]);
}
public function login(Request $request)
{
$data = $request->all();
$validator = Validator::make($request->all(), [
'email' => 'email|required',
'password' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors()->first(), 422);
}
if (!auth()->attempt($data)) {
return response()->json('ایمیل یا رمز عبور اشتباه است.', 422);
}
$user = auth()->user();
$tokenResult = $user->createToken('userToken');
$tokenModel = $tokenResult->token;
if ($request->remember_me)
$tokenModel->expires_at = Carbon::now()->addWeeks(1);
$tokenModel->save();
return response()->json([
'user' => new UserResource($user),
'token' => $tokenResult->accessToken,
'token_type' => 'Bearer'
]);
}
public function logout(Request $request)
{
/** @var User $user
*/
$request->user()->token()->revoke();
return response()->json('شما با موفقیت خارج شدید.');
}
}
با مشاهدهی متد login، میبینید که ابتدا ایمیل و رمز عبور ارسالی کاربر را اعتبارسنجی میکنیم. سپس با استفاده از تابع کمکی ()auth، عملیات احراز هویت را انجام میدهیم. نمونهی ساخته شده از کلاس PersonalAccessTokenResult را در متغیر tokenResult ذخیره میکنیم. خصوصیت token در این کلاس، حاوی مدل رکورد ایجاد شده در جدول oauth_access_tokens در دیتابیس است که توکنهای دسترسی شخصی را در خود ذخیره میکند. در صورتی که کاربر گزینهی Remember Me یا مرا به خاطر بسپار را انتخاب کرده باشد، میتوان تاریخ انقضای توکن ایجاد شده را به دلخواه تغییر داد. در اینجا ما تاریخ انقضا را برابر یک هفته قرار میدهیم و سپس با استفاده از متد ()save، مدل مربوطه را بروزرسانی میکنیم. در انتها نیز، مانند متد register، اطلاعات را در قالب آرایهی Json به سمت کاربر ارسال میکنیم.
برای logout نیز ابتدا با استفاده از متد ()token، مدل رکورد مربوط به توکن ارسالی با درخواست کاربر را گرفته و با استفاده از متد ()revoke این مدل، آن را باطل میکنیم. سپس میتوانیم یک پیام دلخواه نیز به سمت کلاینت ارسال کنیم.
ایجاد مسیرها
برای تکمیل فرآیند نوشتن وبسرویسها، باید مسیرهای موردنیاز را به شکل زیر در فایل api.php در پوشهی routes تعریف کنیم.
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::group(['prefix' => 'v1'], function (){
Route::post('/register', [\App\Http\Controllers\Api\v1\AuthController::class, 'register']);
Route::post('/login', [\App\Http\Controllers\Api\v1\AuthController::class, 'login']);
Route::middleware('auth:api')->get('/logout', [\App\Http\Controllers\Api\v1\AuthController::class, 'logout']);
});
همانطور که میبینید، مسیرها را با استفاده از پیشوند v1 نسخهبندی کردهایم تا در صورت ارائهی نسخهی بالاتر، مسیرهای مربوط به نسخههای قدیمی تحت تاثیر قرار نگیرند. همچنین برای مسیر logout یا خروج، از یک Middleware با نام auth:api استفاده کردهایم. شما میتوانید از این Middleware برای هر مسیری که نیاز به یک توکن معتبر دارد، استفاده کنید تا دسترسی به این مسیرها محدود شود. قسمت دوم نام این Middleware، مربوط به Guard تعریف شده در فایل AuthServiceProvider است. در صورتی که نیاز به احراز هویت انواع مختلفی از کاربران را دارید و هر نوع کاربر نیز از مدل Eloquent متفاوتی استفاده میکند، میتوانید برای هر کدام، یک Guard در فایل AuthServiceProvider ایجاد کنید و سپس نام Guard دلخواه را در نام Middleware فوق جایگزین کنید.
تست وبسرویسها
پس از اجرای مراحل فوق، اکنون باید وبسرویسهایی که ایجاد کردهایم را تست کنیم. ما این کار را با استفاده از نرمافزار Postman انجام میدهیم. میتوانید این نرمافزار را به صورت رایگان از وب سایت Postman دانلود کنید.
اجرای اپلیکیشن
ابتدا مطمئن شوید که با استفاده از Wamp یا Xampp، ماژول MySql را فعال کردهاید سپس، با استفاده از دستور زیر، اپلیکیشن را اجرا کنید.
php artisan serve
ثبت نام
نرمافزار Postman را باز کنید. با کلیک بر روی گزینهی Create a request، یک پنجرهی ارسال درخواست ایجاد کنید. مسیر ثبت نام را در کادر مربوط به خود وارد کنید و متد Post را انتخاب کنید. در قسمت Body، با انتخاب x-www-form-urlencoded، پارامترهای لازم برای ثبت نام را وارد کنید. مقادیر پارامترها دلخواه هستند.
پس از کلیک بر روی گزینهی Send، پاسخ موردنظر بنا به اطلاعات ارسالی به صورت آرایهی Json به شما نشان داده میشود. در صورت رعایت کلیهی نکات ذکر شده در این مقاله و همچنین استفاده از مقادیر فوق، پاسخ موفقیتآمیز به صورت زیر برایتان نمایش داده میشود.
در این حالت، یک کاربر با اطلاعات فوق در دیتابیس شما ایجاد شده است.
ورود
برای ورود نیز پنجرهی دیگری را باز کرده و مانند قبل، مسیر، متد و پارامترهای لازم را وارد کنید. سپس بر روی دکمهی Send کلیک کنید. در صورت ورود صحیح اطلاعات، با پاسخ موفقیت آمیز زیر روبرو میشوید.
خروج
چه پس از ثبت نام و چه پس از ورود، در صورت دریافت موفقیت آمیز توکن، میتوانید به تمام مسیرهای ویژهی کاربران دسترسی داشته باشید. کافی است که توکن ایجاد شده را در در قسمت Header Authorization کپی کنید.
به عنوان مثال، برای ارسال درخواست خروج باید توکن ایجاد شده را به همراه درخواست خود به سمت سرور ارسال کنید. برای این منظور، یک پنجرهی جدید باز کرده و مانند قبل، مسیر را وارد و متد را انتخاب کنید. این بار، در قسمت Authorization، در سمت چپ و از منوی آبشاری Type، گزینهی Bearer Token را انتخاب کنید. سپس توکن را در کادر سمت راست کپی کنید. پس از کلیک بر روی گزینهی Send، توکن با موفقیت باطل میشود.
در این مقاله از سری مقالات آموزش لاراول، نحوهی احراز هویت وبسرویسهای Restful را با ایجاد توکنهای دسترسی شخصی و استفاده از پکیج Passport در لاراول آموختیم و توانستیم به صورت عملی با جزییات استفاده از این پکیج آشنا شویم. همچنین توانستیم وب سرویسهای Restful ایجاد شده را با استفاده از نرم افزار Postman تست کنیم و از اجرای موفقیت آمیز آنها مطمئن شویم. با شیوهی نسخهبندی در نوشتن وبسرویس آشنا شدیم و نحوهی ایجاد Resource برای تبدیل مدل یا کالکشن به خروجی Json را فراگرفتیم. شما به همین شیوهی ذکر شده در این مقاله، میتوانید وبسرویسهای دلخواهتان را برای هر اپلیکیشنی که نیاز دارید، ایجاد کنید و با استفاده از ایجاد توکنهای دسترسی شخصی، این وبسرویسها را ایمن کنید.
خوشحال میشویم نظرات، تجربیات و سوالات خود را با ما و سایر کاربران سون لرن به اشتراک بگذارید.
اگر به یادگیری بیشتر لاراول علاقه داری میتوانی در دوره آموزش لاراول کاربردی (بسته پروژه محور) شرکت کنی، این دوره شامل ۱۲ پروژه کاربردی و پر استفاده در دنیای واقعی است، که تمامی پروژهها به صورت کامل برنامه نویسی خواهند شد، تا دانشجو بتواند با روند ایجاد و تکمیل پروژه به صورت کامل آشنا شود.
۹ دیدگاه
۰۶ آبان ۱۴۰۱، ۲۰:۱۱
سلام
خیلی ممنون از مطالب خوب و با ارزشی ک گزاشتید
مشکل حل شد
۰۸ تیر ۱۴۰۱، ۱۰:۱۸
این روش در passport 10 به بعد کار نمیکنه
۲۶ آذر ۱۴۰۰، ۰۹:۲۸
سلام این ارور برای چیه؟
$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Illuminate\Database\QueryException
SQLSTATE[42S01]: Base table or view already exists: 1050 Table &#39;users&#39; already
exists (SQL: create table `users` (`id` bigint unsigned not null auto_increment
primary key, `name` varchar(255) not null, `email` varchar(255) not null, `emai
l_verified_at` timestamp null, `password` varchar(255) not null, `remember_token
` varchar(100) null, `created_at` timestamp null, `updated_at` timestamp null) d
efault character set utf8mb4 collate &#39;utf8mb4_unicode_ci&#39;)
at C:\xampp\htdocs\blog\vendor\laravel\framework\src\Illuminate\Database\Conne
ction.php:703
699▕ // If an exception occurs when attempting to run a query, we&#39;ll
format the error
700▕ // message to include the bindings with SQL, which will make th
is exception a
701▕ // lot more helpful to the developer instead of just the databa
se&#39;s errors.
702▕ catch (Exception $e) {
➜ 703▕ throw new QueryException(
704▕ $query, $this-&gt;prepareBindings($bindings), $e
705▕ );
706▕ }
707▕ }
1 C:\xampp\htdocs\blog\vendor\laravel\framework\src\Illuminate\Database\Conn
ection.php:492
PDOException::(&quot;SQLSTATE[42S01]: Base table or view already exists: 1050 T
able &#39;users&#39; already exists&quot;)
2 C:\xampp\htdocs\blog\vendor\laravel\framework\src\Illuminate\Database\Conn
ection.php:492
PDOStatement::execute()
نازنین کریمی مقدم۲۷ آذر ۱۴۰۰، ۰۸:۴۵
درود
احتمالا وسطش بنا به دلایلی عملیات نیمه تمام مونده.
اگر در حال کار روی دیتابیس لوکال هستید بد نیست جدول رو پاک کنید و دوباره امتحان کنید. یا با استفاده از دستور زیر یکبار ریست انجام بدید:
php artisan migrate:reset
حسین شیری نژاد۲۶ مهر ۱۴۰۰، ۰۶:۵۹
خیلی خیلی خوب بود قربون شما
واقعا به این آموزشهای خوب به زبان فارسی نیاز هست سریع کار آدمو را میندازه.
نازنین کریمی مقدم۲۶ مهر ۱۴۰۰، ۱۲:۳۸
درود
خوشحالیم مقاله براتون مفید بوده دوست عزیز
mohammadhussein۱۵ اردیبهشت ۱۴۰۰، ۱۲:۲۱
اموزش کاملی بود . خیلی ممنون
احمد۱۴ اردیبهشت ۱۴۰۰، ۰۹:۲۹
واقعا عالی بود.
محمدحسین طاهری۱۶ بهمن ۱۳۹۹، ۱۴:۵۷
بسیار عالی و کامل بود، موفق باشید..
شروع رایگان یادگیری برنامه نویسی
کلیک کنید 👇
دوره الفبای برنامه نویسی با هدف انتخاب زبان برنامه نویسی مناسب برای شما و پاسخگویی به سوالات متداول در شروع یادگیری موقتا رایگان شد: