وحدة التحكم

قم بإنشاء ملف وحدة التحكم app/controller/FooController.php.

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function index(Request $request)
    {
        return response('hello index');
    }

    public function hello(Request $request)
    {
        return response('hello webman');
    }
}

عند زيارة http://127.0.0.1:8787/foo، ستظهر الصفحة hello index.

وعند زيارة http://127.0.0.1:8787/foo/hello، ستظهر الصفحة hello webman.

بالطبع يمكنك تغيير قواعد التوجيه من خلال إعدادات التوجيه، راجع التوجيه.

تنبيه
إذا كنت تواجه خطأ 404، يرجى فتح config/app.php، وتعيين controller_suffix إلى Controller، ثم أعد تشغيل التطبيق.

لاحقة وحدة التحكم

من الإصدار 1.3، يدعم Webman تعيين لاحقة وحدة التحكم في config/app.php، إذا كانت controller_suffix في config/app.php فارغة ''، فإن وحدة التحكم ستكون كما يلي

app\controller\Foo.php.

<?php
namespace app\controller;

use support\Request;

class Foo
{
    public function index(Request $request)
    {
        return response('hello index');
    }

    public function hello(Request $request)
    {
        return response('hello webman');
    }
}

يوصى بشدة بتعيين لاحقة وحدة التحكم إلى Controller، حيث يساعد ذلك في تجنب تضارب الأسماء بين وحدات التحكم واسماء النماذج، مما يزيد من الأمان.

الشرح

  • يقوم الإطار تلقائياً بتمرير كائن support\Request إلى وحدة التحكم، من خلاله يمكنك الحصول على بيانات إدخال المستخدم (مثل get, post, header, cookie)، راجع الطلب.
  • يمكن لوحدة التحكم إرجاع أرقام، أو سلاسل نصية، أو كائنات support\Response، ولكن لا يمكنها إرجاع أنواع بيانات أخرى.
  • يمكن إنشاء كائن support\Response من خلال وظائف المساعد مثل response(), json(), xml(), jsonp(), redirect().

ربط معلمات وحدة التحكم

مثال

يدعم Webman ربط معلمات الطلب تلقائيًا بمعلمات وظيفة وحدة التحكم، على سبيل المثال

<?php
namespace app\controller;
use support\Response;

class UserController
{
    public function create(string $name, int $age): Response
    {
        return json(['name' => $name, 'age' => $age]);
    }
}

يمكنك تمرير قيم name و age باستخدام طريقة GET أو POST، أو يمكنك تمرير معلمات name و age من خلال معلمات التوجيه، على سبيل المثال

Route::any('/user/{name}/{age}', [app\controller\UserController::class, 'create']);

الأولوية هي معلمات التوجيه > GET > POST المعلمات.

القيم الافتراضية

افترض أننا نزور /user/create?name=tom، ستحصل على الخطأ التالي

Missing input parameter age

السبب هو أننا لم نمرر معلمة age، يمكننا حل هذه المشكلة من خلال تعيين قيمة افتراضية للمعلمات، على سبيل المثال

<?php
namespace app\controller;
use support\Response;

class UserController
{
    public function create(string $name, int $age = 18): Response
    {
        return json(['name' => $name, 'age' => $age]);
    }
}

نوع المعلمات

عندما نزور /user/create?name=tom&age=not_int، ستحصل على الخطأ التالي

تنبيه
هنا لسهولة الاختبار، نحن نقوم بإدخال المعلمات مباشرة في شريط عنوان المتصفح، يجب في التطوير الفعلي تمرير المعلمات باستخدام طريقة POST.

Input age must be of type int, string given

هذا بسبب أن البيانات المستلمة سيتم تحويلها حسب النوع، وإذا لم يكن بالإمكان تحويلها، سيتم طرح support\exception\InputTypeException استثناء،
ولأن معلمة age المرسلة لا يمكن تحويلها إلى نوع int، ستحصل على الخطأ المذكور أعلاه.

أخطاء مخصصة

يمكننا استخدام الترجمة متعددة اللغات لتخصيص الأخطاء مثل Missing input parameter age و Input age must be of type int, string given،
بالاستناد إلى الأمر التالي

composer require symfony/translation
mkdir resource/translations/zh_CN/ -p
echo "<?php
return [
    'Input :parameter must be of type :exceptType, :actualType given' => 'يجب أن تكون المعلمة المدخلة :parameter من نوع :exceptType، والنوع المقدم هو :actualType',
    'Missing input parameter :parameter' => 'المعلمة المدخلة :parameter مفقودة',
];" > resource/translations/zh_CN/messages.php
php start.php restart

أنواع أخرى

تدعم Webman أنواع المعلمات مثل int, float, string, bool, array, object, كائنات الفئات، على سبيل المثال

<?php
namespace app\controller;
use support\Response;

class UserController
{
    public function create(string $name, int $age, float $balance, bool $vip, array $extension): Response
    {
        return json([
            'name' => $name,
            'age' => $age,
            'balance' => $balance,
            'vip' => $vip,
            'extension' => $extension,
        ]);
    }
}

عند زيارة /user/create?name=tom&age=18&balance=100.5&vip=true&extension[foo]=bar، ستحصل على نتيجة مماثلة كما يلي

{
  "name": "tom",
  "age": 18,
  "balance": 100.5,
  "vip": true,
  "extension": {
    "foo": "bar"
  }
}

كائنات الفئات

يدعم Webman تمرير كائنات الفئات من خلال تحديد نوع المعلمات، على سبيل المثال

app\service\Blog.php

<?php
namespace app\service;
class Blog
{
    private $title;
    private $content;
    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content = $content;
    }
    public function get()
    {
        return [
            'title' => $this->title,
            'content' => $this->content,
        ];
    }
}

app\controller\BlogController.php

<?php
namespace app\controller;
use app\service\Blog;
use support\Response;

class BlogController
{
    public function create(Blog $blog): Response
    {
        return json($blog->get());
    }
}

عند زيارة /blog/create?blog[title]=hello&blog[content]=world، ستحصل على النتيجة التالية

{
  "title": "hello",
  "content": "world"
}

كائنات النماذج

app\model\User.php

<?php
namespace app\model;
use support\Model;
class User extends Model
{
    protected $connection = 'mysql';
    protected $table = 'user';
    protected $primaryKey = 'id';
    public $timestamps = false;
    // هنا يجب إضافة الحقول القابلة للتعبئة، لمنع إدخال حقول غير آمنة من الواجهة الأمامية
    protected $fillable = ['name', 'age'];
}

app\controller\UserController.php

<?php
namespace app\controller;
use app\model\User;
class UserController
{
    public function create(User $user): int
    {
        $user->save();
        return $user->id;
    }
}

عند زيارة /user/create?user[name]=tom&user[age]=18، ستحصل على نتيجة مثل

1

دورة حياة وحدة التحكم

عندما تكون controller_reuse في config/app.php محددة على false، سيقوم كل طلب بتهيئة نسخة وحدة التحكم المقابلة مرة واحدة، ويتم تدمير نسخة وحدة التحكم بعد انتهاء الطلب، وهذا مشابه لآلية التشغيل في الأطر التقليدية.

عندما تكون controller_reuse في config/app.php محددة على true، سيتم إعادة استخدام نسخة وحدة التحكم لجميع الطلبات، وهذا يعني أن نسخة وحدة التحكم ستبقى في الذاكرة بمجرد إنشائها، وستتم إعادة استخدام كل الطلبات لذلك.

تنبيه
عند تفعيل إعادة استخدام وحدة التحكم، يجب ألا يقوم الطلب بتغيير أي خاصية من خصائص وحدة التحكم، لأن هذه التغييرات ستؤثر على الطلبات اللاحقة، على سبيل المثال

<?php
namespace app\controller;

use support\Request;

class FooController
{
    protected $model;

    public function update(Request $request, $id)
    {
        $model = $this->getModel($id);
        $model->update();
        return response('ok');
    }

    public function delete(Request $request, $id)
    {
        $model = $this->getModel($id);
        $model->delete();
        return response('ok');
    }

    protected function getModel($id)
    {
        // ستبقى هذه الميثود تحتفظ بـ model بعد أول طلب update?id=1
        // إذا تم طلب delete?id=2 مرة أخرى، سيتم حذف بيانات 1
        if (!$this->model) {
            $this->model = Model::find($id);
        }
        return $this->model;
    }
}

تنبيه
لن يكون لعودة البيانات في دالة __construct() من وحدة التحكم أي تأثير، على سبيل المثال

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function __construct()
    {
        // لا يوجد تأثير للعودة بالبيانات في دالة الإنشاء، لن يتلقى المتصفح استجابة.
        return response('hello'); 
    }
}

الفرق بين عدم إعادة استخدام وحدة التحكم وإعادة استخدامها

الفرق كما يلي

عدم إعادة استخدام وحدة التحكم

كل طلب سيتم إنشاء نسخة جديدة من وحدة التحكم، ويتم تحرير تلك النسخة بعد انتهاء الطلب واستعادة الذاكرة. عدم إعادة استخدام وحدة التحكم مشابه للأطر التقليدية، ويفي بمعظم عادات المطورين. نظرًا لأن وحدات التحكم يتم إنشاؤها وتدميرها مرارًا وتكرارًا، فإن الأداء سيكون أقل قليلاً مقارنة بوحدات التحكم المعاد استخدامها (حيث أن أداء الضغط نسبة "helloworld" أقل بحوالي 10%، ولكن يمكن تجاهله بشكل أساسي لمشروع تجاري).

إعادة استخدام وحدة التحكم

في حالة إعادة الاستخدام، يتم إنشاء وحدة التحكم مرة واحدة فقط في كل عملية، ولا يتم تحرير نسخة وحدة التحكم بعد انتهاء الطلب، بل سيتم إعادة استخدام هذه النسخة لبقية الطلبات. يعتبر أداء إعادة استخدام وحدة التحكم أفضل، ولكنه لا يتناسب مع عادات معظم المطورين.

لا يمكن استخدام إعادة استخدام وحدة التحكم في الحالات التالية

عندما يتسبب الطلب في تغيير خصائص وحدة التحكم، فلا يمكن تشغيل إعادة استخدام وحدة التحكم، حيث ستؤثر هذه التغييرات على الطلبات اللاحقة.

يحب بعض المطورين القيام ببعض التهيئات لكل طلب في دالة إنشائهم __construct()، ولن يظل هذا ممكنًا مع إعادة استخدام وحدة التحكم، لأن دالة الإنشاء في العملية الحالية ستدعى مرة واحدة فقط، وليس لكل طلب.