وب سرویس : بخش سوم : ساخت وب سرویس با PHP و MyQSL و ارتباط آن با Retrofit

آموزش ساخت فرم ثبت نام اندروید و ارتباط Retrofit با وب سرویس PHP MySQL
در این مبحث یک وب سرویس (API) ساده تحت PHP و دیتابیس MySQL می‌نویسیم و ارتباط آن با یک اپلیکیشن اندرویدی که شامل یک فرم ثبت نام و یک فرم ورود هست را بررسی می‌کنیم. در این پروژه از کتابخانه معروف Retrofit 2 برای ارتباط بین وب سرویس و کلاینت استفاده شده است.
این بخش شامل مباحث زیر می‌باشد:

  • معرفی مختصر زبان PHP
  • معرفی سیستم مدیریت پایگاه داده MySQL
  • معرفی پنل phpMyAdmin جهت مدیریت دیتابیس MySQL
  • نحوه کار با لوکال سرور WampServer
  • نحوه ساخت دیتابیس و جداول در پنل phpMyAdmin
  • ساخت یک وب سرویس ساده تحت زبان PHP و دیتابیس MySQL
  • معرفی و تشریح تابع‌های بکار رفته در سورس وب سرویس
  • نحوه چاپ اطلاعات با فرمت JSON در PHP
  • نحوه تست و خطایابی وب سرویس توسط نرم افزار Postman
  • نحوه تست و خطایابی وب سرویس بدون نرم افزار و توسط URL
  • اضافه کردن کتابخانه‌های Retrofit و Converter Gson به پروژه اندروید
  • تعریف حق دسترسی اپلیکیشن به شبکه و اینترنت
  • معرفی @SerializedName کتابخانه Gson
  • استفاده از متدهای @POST و @GET برای ارسال و دریافت اطلاعات بین کلاینت و API
  • ساخت فرم ثبت نام و ورود توسط Fragment
  • جایگزین کردن فرگمنت‌ها توسط متد replace
  • مدیریت onResponse و onFailure در متد enqueue

این مبحث در قالب PDF و در ۴۷ صفحه تهیه شده که در ادامه چند صفحه‌ی ابتدایی مربوط به بخش‌های “وب سرویس” و “پروژه اندرویدی” را مشاهده می‌کنید:

به نام خدا. در مبحث گذشته با کتابخانه معروف Retrofit و همچنین فرمت انتقال داده JSON آشنا شدیم. پروژه جلسه قبل بسیار ساده بود و تنها شامل دریافت اطلاعات ثابت (استاتیک) از یک فایل json می‌شد.
در این جلسه قصد دارم یک وب سرویس (RESTful API) بسیار ساده و سبک با استفاده از زبان سمت سرور PHP و دیتابیس MySQL بسازم که قابلیت انتقال داده بین آن و یک کلاینت وجود داشته باشد. که در اینجا کلاینت ما یک دیوایس اندرویدی است.
سپس یک پروژه اندروید ایجاد می‌کنم که شامل یک فرم ثبت‌نام (رجیستر) و یک فرم ورود (لاگین) است.
نحوه کار نیز به این صورت است که با تکمیل فرم ثبت‌نام توسط کاربر، اطلاعات وارد شده توسط Retrofit و در بستر HTTP به وب سرویس ارسال شده و داخل دیتابیس ذخیره می‌گردد. همچنین در فرم لاگین، اطلاعاتی که کاربر وارد کرده (نام کاربری و رمز عبور) با اطلاعات ذخیره شده در دیتابیس مطابقت داده شده و در صورت صحت اطلاعات وارد شده، پیغام خوش‌آمد گویی حاوی نام و نام کاربری شخص نمایش داده می‌شود.

تذکر: قبل از مطالعه این مبحث حتما مبحث گذشته را مطالعه کنید. جزئیاتی که قبلا بیان شده در این مبحث تکرار نمی‌شود.

ساخت وب سرویس با استفاده از PHP و MySQL

PHP یک زبان برنامه نویسی شیء‌گراست که برای ساخت برنامه‌های سمت سرور استفاده می‌شود.
MySQL یک سیستم مدیریت پایگاه داده (دیتابیس) است که از طریق آن امکان ذخیره سازی، جستجو، مرتب کردن و بازیابی داده‌های دیتابیس فراهم می‌گردد. در حال حاضر دیتابیس تعداد زیادی از وب‌سایت‌ها توسط این سیستم مدیریت می‌شوند. در صورتی که قبلا با سرویس‌های میزبانی سایت (هاستینگ) کار کرده‌اید و تجربه راه اندازی یک وب سایت را دارید (مانند یک وب سایت وردپرسی) احتمال زیاد با MySQL و پنل مدیریت phpMyAdmin آشنا هستید. MySQL هم مانند SQLite رایگان بوده که تحت مالکیت شرکت Oracle قرار دارد.
در اینجا قصد آموزش زبان PHP یا تشریح کامل MySQL را ندارم. اگر با کلیّت زبان‌های برنامه نویسی آشنایی مختصری داشته باشید، کدهای PHP وب سرویس را به راحتی درک می‌کنید. با دستورهای زبان SQL هم قبلا در مبحث SQLite آشنا شدیم.
ابتدا WampServer را اجرا می‌کنم. در مبحث قبل با ومپ سرور آشنا شدیم و گفتیم که به واسطه این نرم افزار می‌توانیم یک وب سرور محلی روی رایانه شخصی خودمان راه اندازی کنیم.

تذکر: وب سرور را با وب سرویس اشتباه نگیرید. وب سرویس یک نرم افزار است که روی وب سرور اجرا می‌شود. WampServer یک وب سرور را روی رایانه ما راه اندازی می‌کند که قابلیت پشتیبانی از زبان PHP و همچنین MySQL را داراست. یعنی دقیقا همان چیزی که ما برای راه اندازی وب سرویس این مبحث نیاز داریم. ضمنا در مبحث قبل به این نکته اشاره شد که علاوه بر WampServer گزینه‌های دیگری مانند Xampp نیز وجود دارند و انتخاب آن به سلیقه شما بستگی دارد. و البته اینکه WampServer فقط نسخه ویندوزی دارد.

بعد از اجرای WampServer چند ثانیه زمان لازم است تا سرویس‌های آن لود شوند. در صورتی که سرویس‌ها به درستی لود شد، آیکون نرم افزار به رنگ سبز تغییر کرده و عبارت All Services Running را نشان می‌دهد:

اجرای لوکال سرور WampServer

اگر آیکون سبز نشد روی آن راست کلیک و گزینه Refresh را انتخاب کنید.
با کلیک روی آیکون ومپ سرور، منوی زیر باز می‌شود:

منوی WampServer

گزینه Localhost صفحه اصلی WampServer را در مرورگر باز می‌کند که اطلاعات مربوط به ماژول‌های فعال را نشان می‌دهد. phpMyAdmin پنل مدیریت MySQL است که برای ساخت و مدیریت دیتابیس از این پنل استفاده می‌کنیم. در ادامه گزینه‌های دیگری برای مدیریت قسمت‌های مختلف قرار دارد. در صورتی که هنوز آیکون نرم افزار سبز نشده یکبار گزینه Restart All Services را بزنید.
ابتدا باید یک دیتابیس بسازم. برای اینکار روی phpMyAdmin کلیک می‌کنم. صفحه ورود به پنل در مرورگر باز می‌شود:

ورود به پنل phpMyAdmin

در WampServer یا سایر Local Server ها، مطابق تصویر بالا Username برابر با root و Password باید خالی باشد. پس از ورود، پنل به صورت زیر باز می‌شود:

پنل phpMyAdmin

به قسمت Databases می‌روم و یک دیتابیس با نام دلخواه userdata ایجاد می‌کنم:

ساخت دیتابیس در phpMyAdmin

پس از ساخت دیتابیس یک table با نام دلخواه info به دیتابیس اضافه می‌کنم. من ۴ ستون برای این جدول نیاز دارم بنابراین در قسمت Number of columns عدد ۴ قرار می‌دهم. این چهار قسمت برای ذخیره اطلاعات کاربران است که شامل شناسه (id)، نام فرد (name)، نام کاربری (username) و رمز عبور (password) می‌شود.

ساخت جدول در دیتابیس MySQL

table دیتابیس

سطر id را از نوع INT با اندازه ۵ و مابقی سطرها از نوع VARCHAR و اندازه ۵۰ درنظر گرفتم. id می‌بایست برای هر کاربر منحصر بفرد بوده و به صورت خودکار با ثبت نام هر کاربر یک واحد به آن اضافه شود بنابراین برای این سطر قسمت A I (مخفف AUTO INCREMENT) را تیک میزنم:

Auto Increment

ساخت table در دیتابیس

درباره Type و Length و سایر موارد، مفصل در مبحث SQLite بحث شده است و نیازی به تکرار نیست. در اینجا من ساخت جدول و ستون را در محیط ویژوال انجام دادم اما اینکار را با نوشتن Query هم می‌توان انجام داد. روی Preview SQL کلیک کنید. کوئری زیر نشان داده می‌شود:

کوئری ساخت جدول دیتابیس

یعنی اگر نوشتن کوئری را بلد باشیم با قرار دادن آن در قسمت SQL نیز می‌توانیم جدول را ایجاد کنیم:

کوئری SQL

بهرحال هردو روش قابل انجام است. من در همان مرحله قبل یعنی محیط ویژوال بعد از ورود اطلاعات مربوط به سطرها، گزینه Save را می‌زنم و ستون‌ها ساخته می‌شود:

ُاخت جدول دیتابیس

خب! فعلا با دیتابیس کاری ندارم.
در فولدر www مربوط به WampServer یک فولدر با نام دلخواه api می‌سازم و فایل‌هایی که قبلا آماده کرده‌ام را به اینجا منتقل می‌کنم:

سورس وب سرویس PHP و MySQL

من این وب سرویس ساده را با زبان PHP نوشته‌ام بنابراین پسوند فایل‌ها php. است. کدهای مربوط به هر فایل را به صورت خلاصه توضیح می‌دهم تا با کلیت نحوه کارکرد این وب سرویس آشنا شوید. برای باز کردن فایل‌ها از یک ادیتور ساده و رایگان مانند ++Notepad یا Sublime Text استفاده کنید (سورس وب سرویس در فولدر Exercises قرار دارد).
db_connect.php:
در این فایل اطلاعات مربوط به دیتابیس را وارد کرده‌ام و سپس دستور اتصال به دیتابیس نوشته شده:

فایل db_connect.php

چهار متغیر با نام‌های دلخواه ساخته‌ام که اطلاعات دیتابیس را شامل می‌شود. دیتابیس روی همین سیستم میزبانی می‌شود بنابراین مقدار آن localhost است. user و password همان یوزر و پسورد ورود به phpMyAdmin است. یعنی root برای user و رشته خالی “” برای password. نام دیتابیس را هم درون متغیر dbname ذخیره کردم. حتما می‌دانید نامگذاری متغیرها مانند آنچه در جاوا قبلا آشنا شده‌اید، دلخواه بوده و قانونی وجود ندارد.
در ادامه توسط تابع mysqli_connect() یک کانکشن برای دیتابیس ایجاد می‌کنم. این تابع ۴ مقدار ورودی گرفته که همان اطلاعاتی است که در ابتدای فایل تعریف شده. برای آنکه مطمئن شوم دستور اتصال را به درستی نوشته‌ام و ارتباط با دیتابیس برقرار است، یک if() نوشتم که اتصال یا عدم اتصال را بررسی می‌کند. اگر $connection برقرار باشد (یعنی mysqli_connect با اطلاعاتی که گرفته توانسته به دیتابیس متصل شود) پیغامی مبنی بر اتصال موفق و در غیر اینصورت پیغام شکست در اتصال چاپ می‌شود. برای بررسی نتیجه کار کافیست فایل را روی مرورگر اجرا کنم:

تست اتصال به دیتابیس

پیغام اول چاپ شد یعنی در اتصال به دیتابیس مشکلی وجود ندارد. پس از حصول اطمینان از صحت عملکرد اتصال به دیتابیس، دستور if را حذف یا Comment کنید تا از خروجی نهایی وب سرویس حذف شود:

کامنت کردن در PHP

بعد از غیر فعال شدن این دستور، با فراخوانی مجدد فایل در مرورگر نباید هیچ خروجی چاپ شود:

خروجی وب سرویس اندروید

در ابتدای جلسه تصمیم گرفتیم اپلیکیشن ما یک فرم ثبت نام و یک فرم ورود داشته باشد. بنابراین دو فایل جداگانه برای مدیریت این قسمت‌ها به وب سرویس اضافه شده.

register.php:

بخش عضویت وب سرویس

ابتدا توسط دستور require فایل db_connect.php فراخوانی شده است زیرا برای دریافت اطلاعات کاربر و ثبت داخل دیتابیس نیاز به اتصال به دیتابیس داریم. در فرم ثبت نام، اطلاعات شامل نام، نام کاربری و رمز عبور از کاربر دریافت می‌شود. سه متغیر $name، $username و $password توسط متد $_GET داده‌های ارسالی از سمت کلاینت را دریافت و درون خود ذخیره می‌کنند.
در فرایند ثبت نامِ کاربر باید بررسی شود نام کاربری که انتخاب کرده قبلا توسط شخص دیگری انتخاب نشده باشد. بنابراین یک متغیر با نام $sql_select نوشتم که مقدار آن یک کوئری از نوع SELECT است:

SELECT * FROM info WHERE username = '$username'

این دستور (کوئری) جدول info درون دیتابیس را بررسی می‌کند و اگر در ستون username مقداری برابر با مقدار ذخیره شده در متغیر $username که از کلاینت (فرم ثبت نام) دریافت شده وجود داشته باشد آنرا برمی‌گرداند.
در متغیر $result نیز تابع mysqli_query() ابتدا به دیتابیس متصل می‌شود ($connection)، سپس دستور فوق را به دیتابیس ارسال می‌کند. در نهایت آنچه از این کوئری حاصل می‌شود (یعنی وجود یا عدم وجود username مشابه با آنچه کاربر فعلی قصد ثبت نام دارد) در این متغیر ذخیره می‌شود.
سپس یک شرط داریم. تابع mysqli_num_rows() در PHP تعداد سطرهای برگشت شده از کوئری را اعلام می‌کند (num مخفف number به معنی تعداد و rows به معنی سطرها).
mysqli_num_rows($result) > 0
اگر username ای که شخص وارد کرده قبلا توسط کاربر دیگری انتخاب شده باشد، $result آنرا برمی‌گرداند پس mysqli_num_rows() بزرگتر از ۰ خواهد بود و در نتیجه قسمت اول شرط اجرا می‌شود:

$status = "REGISTERED";

یعنی رشته متنی REGISTERED درون متغیر $status ذخیره می‌گردد.
ولی اگر کاربری قبلا با این نام کاربری ثبت نام نکرده باشد، mysqli_num_rows($result) حاصلش ۰ است بنابراین قسمت دوم شرط اجرا می‌شود. یعنی کد درون بدنه else.
در خط ۱۵ یک کوئری با نام $sql_insert تعریف کردم. این کوئری، مقادیر ذخیره شده در متغیرهای $name، $username و $password را درون جدول info و درسطرهای name، username و password وارد می‌کند. برای اینکه مطمئن شویم اطلاعات به درستی درون دیتابیس ذخیره شده یا نه، یک شرط بعد از این کوئری اضافه کردم که اگر کوئری به دیتابیس ارسال شده، عبارت SUCCESS و در غیر اینصورت عبارت ERROR در متغیر $status ذخیره شود.

پس تا اینجا متغیر $status می‌توانید سه وضعیت را در خود ذخیره کند:
– نام کاربری قبلا استفاده شده و پیغام REGISTERED را برمی‌گرداند.
– نام کاربری آزاد است، عملیات ثبت نام با موفقیت انجام می‌شود و اطلاعات کاربر درون دیتابیس ذخیره می‌گردد و در نهایت SUCCESS را برمی‌گرداند.
– نام کاربری آزاد است ولی به دلایلی اطلاعات دریافتی روی دیتابیس ثبت نمی‌شود که ERROR را برمی‌گرداند.

در نهایت توسط تابع json_encode() مقدار ذخیره شده در $status در قالب یک آبجکت JSON چاپ (echo) می‌شود:

echo json_encode(array("response"=>$status));

خروجی این خط یک آبجکت JSON است که یک جفت نام/مقدار دارد. من نام آنرا response تعیین کردم. مقدار نیز همان $status است.
در انتها کانکشنی که به دیتابیس زده شده توسط تابع mysqli_close() بسته می‌شود زیرا تا عملیات بعدی به این کانکشن نیازی نداریم.

نکته: قبلا گفتیم همه زبان‌های روز دنیا از فرمت JSON پشتیبانی می‌کنند. در PHP برای ارسال داده به سمت کلاینت از تابع json_encode() استفاده می‌شود.

login.php:

بخش ورود وب سرویس

عملیات مرتبط با لاگین کاربری که قبلا ثبت نام کرده از طریق این فایل انجام می‌پذیرد. ابتدا username و password ای که کاربر در فرم لاگین وارد کرده، به سرور ارسال و در متغیرهای $username و $password ذخیره می‌شود. سپس یک کوئری از نوع SELECT نوشتم که username و password ارسالی از سمت کلاینت را در جدول info دیتابیس جستجو می‌کند و در صورتی که این نام کاربری و رمز عبور در دیتابیس موجود باشد، نام (name) را برمی‌گرداند.
تابع $result نقشی مشابه تابع موجود در register.php دارد.
سپس یک شرط تعیین شده که اگر حاصل mysqli_num_rows($result) بزرگتر از صفر بود، توسط تابع mysqli_fetch_assoc() مقدار name از سطر مربوطه در دیتابیس درون متغیر $name ذخیره گردد. به علاوه اینکه عبارت SUCCESS به عنوان status ثبت شود. در ادامه، وضعیت (status)، نام کاربر (name) و نام کاربری وی (username) با فرمت JSON چاپ شود.

نکته: مقدار username از $username گرفته می‌شود که درواقع همان چیزی است که کاربر وارد کرده و از دیتابیس گرفته نمی‌شود.

اگر حاصل mysqli_num_rows($result) برابر با ۰ باشد، وضعیت FAILED چاپ می‌شود.
خب! کارِ نوشتن وب سرویس تمام شد. قبل از اینکه سراغ اندروید استودیو بروم، ابتدا می‌خواهم وب سرویس را تست کنم تا مطمئن شوم عملیات ذخیره و بازیابی داده‌ها به درستی انجام می‌پذیرد و فرمت خروجی JSON آن صحیح و بدون نقص است.

(جهت مطالعه ادامه توضیحات وب سرویس، فایل PDF را دانلود کنید)

ساخت پروژه اندروید اتصال به وب سرویس توسط کتابخانه Retrofit

سراغ اندروید استودیو می‌روم و یک پروژه با نام Retrofit & PHP و اکتیویتی از نوع Empty Activity می‌سازم.
اولین قدم، اضافه کردن کتابخانه‌های Retrofit به build.gradle است:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
}

یادتان نرود برای اتصال به شبکه و اینترنت حتما حق دسترسی لازم را باید به مانیفست اضافه کنیم.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ir.android_studio.retrofitphp">

    <uses-permission android:name="android.permission.INTERNET" />
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

داده‌هایی که از وب سرویس دریافت می‌کنیم response (مشترک در هر دو فرم) و همچنین name و username برای فرم ورود هستند. بنابراین یک کلاس می‌سازم و این سه پارامتر را تعریف می‌کنم. من نام این کلاس را User می‌گذارم:

ساخت کلاس User.java در پروژه اندرویدی

سه متغیر داخل کلاس تعریف کردم. سپس متدهای Getter را می‌سازم (alt+insert)
نامگذاری‌های فوق با نام‌هایی که در وب سرویس انتخاب کرده بودم متفاوت است. بنابراین لازم است تعیین کنیم هر متغیر مربوط به کدام مورد است. برای اینکار از annotation با نام @SerializedName استفاده می‌کنیم که مربوط به کتابخانه Gson است:

User.java:

package ir.android_studio.retrofitphp;

import com.google.gson.annotations.SerializedName;

public class User {

    @SerializedName("response")
    private String apiResposnse;

    @SerializedName("name")
    private String apiName;

    @SerializedName("username")
    private String apiUsername;

    
    public String getApiResposnse() {
        return apiResposnse;
    }

    public String getApiName() {
        return apiName;
    }

    public String getApiUsername() {
        return apiUsername;
    }
}

شاید بپرسید خب چرا کار را سخت کنیم؟ می‌شد متغیرها را همنام با پارامترهای موجود در وب سرویس تعریف کرد تا نیاز به این عمل اضافی نباشد (مانند مبحث گذشته). انتقاد شما بجاست. اما ممکن است روال نامگذاری توسعه دهنده با آنچه از قبل در وب سرویس تعریف شده (و امکان تغییر سورس وب سرویس میسر نیست) متفاوت باشد و برنامه نویس مصمم به نامگذاری به شیوه خودش باشد. در اینجا این راهکار را کتابخانه Gson در اختیار ما قرار داده است.
برای ساخت Retrofit یک کلاس جدید با نام دلخواه API ساختم. کد آن به صورت زیر است.

API.java:

package ir.android_studio.retrofitphp;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class API {

    public static final String BASE_URL = "http://192.168.56.1/api/";
    public static Retrofit myRetrofit = null;

    public static Retrofit getAPI() {

        if (myRetrofit == null) {

            myRetrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();

        }

        return myRetrofit;

    }

}

مانند مبحث قبل ابتدا URL مربوط به وب سرویس را تعریف کردم. سپس یک شرط تعیین شده تا بررسی کند اگر Retrofit برابر null بود آنگاه Retrofit.Builder() را بسازد (اگر null نباشد یعنی قبلا ساخته شده).
نوبت ساخت Interface است. مانند جلسه قبل یک کلاس جدید می‌سازم و نوع آنرا بجای Class از نوع Interface تعیین می‌کنم. من نام ApiInterface را روی این فایل گذاشتم.

ApiInterface.java:

package ir.android_studio.retrofitphp;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

public interface ApiInterface {

    @POST("register.php")
    Call<User> regCall(@Query("name") String Name, @Query("username") String UserName,
@Query("password") String Password);

    @GET("login.php")
    Call<User> loginCall(@Query("username") String UserName, @Query("password") String Password);
    
}

در پروژه مبحث قبل فقط یک متد @GET داشتیم اما اینجا یک متد @POST هم برای بخش عضویت کاربر استفاده می‌شود که داده‌های کاربر باید به سمت سرور ارسال شود. البته همانطور که در قسمت کار با Postman اشاره شد، در هردو قسمت (یعنی ثبت نام و ورود) می‌توان از @GET استفاده کرد چون ما مستقیما از کوئری‌ها استفاده می‌کنیم. با اینحال از @POST هم استفاده کردم تا برای شما ناآشنا نباشد.

توجه : سورس پروژه اندروید و وب سرویس درون پوشه Exercises قرار دارد

جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید.

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

در صورتی که در دانلود از طریق باکس زیر (سبد دانلود) با مشکل مواجه شدید، با مراجعه به این لینک گزینه RetrofitPHP به مبلغ ۵,۰۰۰ تومان را انتخاب نمائید.

دانلود فایل این آموزش با فرمت PDF به همراه سورس وب سرویس و پروژه
تعداد صفحات : ۴۷
حجم : ۳٫۵ مگابایت
قیمت : ۵,۰۰۰ تومان
یک دیدگاه بنویسید

پرسش‌های زیر تایید و پاسخ داده نخواهد شد:
۱ : جزء موارد پاسخ داده شده در مطلب "مشکلات و پرسش های رایج" باشد
۲ : سوال قبلا توسط کاربران در دیدگاهها مطرح و پاسخ داده شده باشد
۳ : سوال خارج از مبحث آموزشی موجود در این صفحه باشد

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

کد امنیتی *