السلام عليكم ورحمة الله وبركاتة
اليوم بإذن الله سنتحدث عن نمط من أنماط التصميم وهو النمط المفرد Singleton Pattern .
- ما هو الـSingleton Pattern ؟
- إن أردت أن تفهم ماذا تعنى هذه الكلمة أكمل معنا الشرح التالى :
أولاً : كنا قد تحدثنا عن ملخص سريع عن البرمجة الكائنية فى هذا الموضوع , الآن نريد عمل دالة تقوم بإنشاء كائن من نفس الفئة كالتالى :
<?php
class singleton {
// دالة إنشاء كائن من نفس الفئة الحالية
public function getObj(){
echo "make object<br>";
return new singleton();
}
// دالة إختبار
public function test(){
echo "ahmed<br>";
}
} // نهاية الفئة
$obj = new singleton();
$obj2 = $obj->getObj();
$obj3 = $obj->getObj();
$obj4 = $obj->getObj();
// test
echo "-------------<br>";
$obj->test();
$obj2->test();
$obj3->test();
$obj4->test();
?>الآن ستسألنى وما الفائده من هذه الدالة؟ فأنا أستطيع عمل كائن مباشراً بدون إستخدام هذه الدالة , فقط أردت إظهار كلمة make object فى كل مره يتم فيها إنشاء كائن من نفس الفئة ولهذا ظهرت هذه الكلمة 3 مرات لأننا أنشئنا كائن من خلال دالة getObj() ثلاث مرات ولكن جرب الكود التالى :
<?php
class singleton {
// متغير لحفظ الكائن المشأ من هذه الفئة
private $classObj = NULL;
// دالة إنشاء كائن من نفس الفئة الحالية
public function getObj(){
if(!$this->classObj){
echo "make object<br>";
$this->classObj = new singleton();
}
return $this->classObj;
}
// دالة إختبار
public function test(){
echo "ahmed<br>";
}
} // نهاية الفئة
$obj = new singleton();
$obj2 = $obj->getObj();
$obj3 = $obj->getObj();
$obj4 = $obj->getObj();
// test
echo "-------------<br>";
$obj->test();
$obj2->test();
$obj3->test();
$obj4->test();
?>- قمنا بإنشاء متغير $classObj لنخزن به كائن منشأ من نفس الفئة ولذلك وضعنا شرط فى حالة عدم وجود قيمة فى المتغير $classObj يتم إنشاء كائن من الفئة singleton وإسناده لهذا المتغير وإن كان يحتوى على قيمة يعود مباشراً بالقيمة الموجودة به بدون إنشاء كائن جديد من الفئة , وسوف تلاحظ هذا فى أن الكلمة make object لم تكتب إلا مرة واحده دليل على عدم إنشاء كائن من الفئة مرة أخرى ولكن إستخدام الكائن المنشأ مسبقاً من هذه الفئة .
ملاحظة : يمكن الإستغناء عن كتابة اسم الفئة بإستخدام self() كالتالى :
public function getObj(){
if(!$this->classObj){
echo "make object<br>";
$this->classObj = new self();
}
return $this->classObj;
}- ولكن يمكن لمستخدم الفئة إنشاء كائن مباشراً دون إستخدام دالة الإنشاء getObj() ولهذا يفضل إلغاء إمكانية إنشاء كائن من هذه الفئة , سيتبادر لذهنك وكيف سيمكننى فى البداية إنشاء كائن من الفئة لأستطيع إستخدام هذه الدالة ؟ أذكرك بالمكونات الساكنة للفئة static ويمكن من خلال تعريف دالة من النوع الساكن الوصول إليها مباشراً بدون إنشاء كائن من الفئة بإستخدام اسم الفئة ثم العلامتين :: ثم اسم الدالة الساكنة , وعند إستخدام دالة ساكنة يجب أيضاً إستخدام متغيرات ساكنة داخل هذه الدالة الساكنة وعلى هذا سيتم تعريف المتغير $classObj على أنه ساكن وسيتم إستخدامه من خلال الكلمة المحجوزة self ثم العلامتين :: ثم اسم المتغير الساكن .
- وهذا الكود لتوضيح ما سبق :
<?php
class singleton {
// متغير لحفظ الكائن المشأ من هذه الفئة
private static $classObj = NULL;
// جعل دالة البناء نهائية لعدم إنشاء كائن مباشراً من خلال اسم الفئة
final protected function __construct(){}
// دالة إنشاء كائن من نفس الفئة الحالية
public static function getObj(){
if(!self::$classObj){
echo "make object<br>";
self::$classObj = new self();
}
return self::$classObj;
}
// دالة إختبار
public function test(){
echo "ahmed<br>";
}
} // نهاية الفئة
$obj = singleton::getObj();
//$obj = new singleton();
$obj2 = $obj->getObj();
$obj3 = $obj->getObj();
$obj4 = $obj->getObj();
// test
echo "-------------<br>";
$obj->test();
$obj2->test();
$obj3->test();
$obj4->test();
?>- تم تعريف دالة بناء الفئة __construct() على أنها نهائية final وبالتالى لا نستطيع إنشاء كائن من هذه الفئة إلا من خلال الدالة الساكنة getObj() , لاحظ الكود الموجود فى التعليق وهو محاولة إنشاء كائن من الفئة مباشراً فلو تم تفعيل الكود سيعطى خطأ .
- أيضاً لمنع عملية نسخ كائن من كائن بإستخدام clone سنقوم بتعريف الدالة __clone() على أنها نهائية لنمنع عملية نسخ كائن جديد .
وتصبح الفئة على الصورة التالية :
<?php
class singleton {
private static $classObj = NULL;
final protected function __construct(){}
final private function __clone() {}
public static function getObj(){
if(!self::$classObj)
self::$classObj = new self();
return self::$classObj;
}
}
?>- وبالتالى قصرنا عملية إنشاء الكائن على الدالة getObj() لضمان عدم إنشاء كائن جديد من هذه الفئة فى حالة وجود كائن منشئ من تلك الفئة .
- سنأخذ تطبيق لهذه الطريقة على كيفية الإتصال الأمثل بقاعدة البيانات ليتضح فائدة هذه الطريقة , سيتم إستخدام mysqli للإتصال بقاعدة البيانات وهى فئة مدمجة باللغة يتم إنشاء كائن منها دون الحاجة لتضمين ملف خارجى .
ملاحظة : إستخدام mysql عفا عليه الزمن والأفضل إستخدام mysqli لمعالجتها كثير من مشاكل mysql وأهمها الناحية الأمنية .
<?php
class mySQL {
// مصفوفة لتخزين قيم الإتصال بقاعدة البيانات بها
private $DB = array();
// متغير لحفظ الكائن المشأ من هذه الفئة
private static $classObj = NULL;
final protected function __construct(){
// ضبط قيم الإتصال بقاعدة البيانات عند إستدعاء الفئة
// اسم السرفر لقاعدة البيانات
$this->DB['Host'] = "localhost";
// اسم المستخدم لقاعدة البيانات
$this->DB['UserName'] = "root";
// الرقم السرى لمستخدم قاعدة البيانات
$this->DB['UserPass'] = "";
// اسم قاعدة البيانات
$this->DB['Name'] = "db";
}
// جعل الدالة نهائية حتى لا يتم إستنساخ كائن جديد من الفئة
final private function __clone() {}
// دالة إنشاء كائن من نفس الفئة الحالية
public static function getObj(){
if(!self::$classObj)
self::$classObj = new self();
return self::$classObj;
}
// دالة إنشاء كائن من فئة الإتصال بقاعدة البيانات
public function getConObj(){
echo "make mysqli object<br>";
return new mysqli($this->DB['Host'],$this->DB['UserName'],$this->DB['UserPass'],$this->DB['Name']);
}
}
$obj = mySQL::getObj();
$obj2 = $obj->getConObj();
$obj3 = $obj->getConObj();
$obj4 = $obj->getConObj();
$obj5 = $obj->getConObj();
$obj6 = $obj->getConObj();
$obj7 = $obj->getConObj();
$obj8 = $obj->getConObj();
$obj9 = $obj->getConObj();
$obj10 = $obj->getConObj();
?>- يجب وضع قيم الإتصال بقاعدة البيانات فى بانى الفئة حتى يعمل معك الكود بشكل سليم بدون أخطاء الإتصال بقاعدة البيانات , فى الكود أضع القيم الإفتراضية للسرفر المحلى "ربما تختلف من سرفر محلى لأخر" .
- تم إنشاء الدالة getConObj() والتى تعود بكائن فئة الإتصال بقاعدة البيانات .
- تم إنشاء كائن من الفئة mySQL من خلال الدالة getObj() كما تقدم شرحه .
- تم إنشاء عده كائنات من فئة الإتصال بقاعدة البيانات من خلال الدالة getConObj() , وتعمدت وضع عدد كبير من الكائنات المنشأه لتلاحظ الوقت الذى يمر حتى يتم إنشاء تلك الكائنات .
سنقوم الآن بتعديل الكود السابق وتطبيق مبدأ مشاركة الإتصال بقاعدة البيانات وذلك من خلال تعريف متغير لحفظ كائن الإتصال المنشأ كالتالى :
<?php
class mySQL {
// مصفوفة لتخزين قيم الإتصال بقاعدة البيانات بها
private $DB = array();
// متغير لحفظ الكائن المشأ من هذه الفئة
private static $classObj = NULL;
// متغير لحفظ الكائن المنشأ من فئة الإتصال بقاعدة البيانات
private $objCon = NULL;
final protected function __construct(){
// ضبط قيم الإتصال بقاعدة البيانات عند إستدعاء الفئة
// اسم السرفر لقاعدة البيانات
$this->DB['Host'] = "localhost";
// اسم المستخدم لقاعدة البيانات
$this->DB['UserName'] = "root";
// الرقم السرى لمستخدم قاعدة البيانات
$this->DB['UserPass'] = "";
// اسم قاعدة البيانات
$this->DB['Name'] = "db";
}
// جعل الدالة نهائية حتى لا يتم إستنساخ كائن جديد من الفئة
final private function __clone() {}
// دالة إنشاء كائن من نفس الفئة الحالية
public static function getObj(){
if(!self::$classObj)
self::$classObj = new self();
return self::$classObj;
}
// دالة إنشاء كائن من فئة الإتصال بقاعدة البيانات
public function getConObj(){
if(!$this->objCon){
echo "make mysqli object<br>";
$this->objCon = new mysqli($this->DB['Host'],$this->DB['UserName'],$this->DB['UserPass'],$this->DB['Name']);
}
return $this->objCon;
}
}
$obj = mySQL::getObj();
$obj2 = $obj->getConObj();
$obj3 = $obj->getConObj();
$obj4 = $obj->getConObj();
$obj5 = $obj->getConObj();
$obj6 = $obj->getConObj();
$obj7 = $obj->getConObj();
$obj8 = $obj->getConObj();
$obj9 = $obj->getConObj();
$obj10 = $obj->getConObj();
?>والآن هل لاحظت فرق الوقت بين هذا الكود والكود السابق
ويمكن تعديل دالة إنشاء كائن من فئة الإتصال بحيث يتم إنهاء الفئة وعرض أخطاء الإتصال بقاعدة البيانات كالتالى :
public function getConObj(){
if(!$this->objCon){
$this->objCon = new mysqli($this->DB['Host'],$this->DB['UserName'],$this->DB['UserPass'],$this->DB['Name']);
if($this->objCon->connect_errno)
die($this->objCon->connect_error);
}
return $this->objCon;
}- سنضع الآن دالة لعمل إستعلام على قاعدة البيانات وإظهار الأخطاء إن وجدت فى الإستعلام كالتالى :
public function makeQuery($qu){
$temp = $this->getConObj()->query($qu);
if(!$temp)
die($this->getConObj()->error);
return $temp;
}- لتصبح الفئة على الشكل التالى:
<?php
class mySQL {
// مصفوفة لتخزين قيم الإتصال بقاعدة البيانات بها
private $DB = array();
// متغير لحفظ الكائن المشأ من هذه الفئة
private static $classObj = NULL;
// متغير لحفظ الكائن المنشأ من فئة الإتصال بقاعدة البيانات
private $objCon = NULL;
final protected function __construct(){
// ضبط قيم الإتصال بقاعدة البيانات عند إستدعاء الفئة
// اسم السرفر لقاعدة البيانات
$this->DB['Host'] = "localhost";
// اسم المستخدم لقاعدة البيانات
$this->DB['UserName'] = "root";
// الرقم السرى لمستخدم قاعدة البيانات
$this->DB['UserPass'] = "";
// اسم قاعدة البيانات
$this->DB['Name'] = "db";
}
// جعل الدالة نهائية حتى لا يتم إستنساخ كائن جديد من الفئة
final private function __clone() {}
// دالة إنشاء كائن من نفس الفئة الحالية
public static function getObj(){
if(!self::$classObj)
self::$classObj = new self();
return self::$classObj;
}
// دالة إنشاء كائن من فئة الإتصال بقاعدة البيانات
public function getConObj(){
if(!$this->objCon){
$this->objCon = new mysqli($this->DB['Host'],$this->DB['UserName'],$this->DB['UserPass'],$this->DB['Name']);
if($this->objCon->connect_errno)
die($this->objCon->connect_error);
}
return $this->objCon;
}
// دالة إنشاء إستعلام على قاعدة البيانات
public function makeQuery($qu){
$temp = $this->getConObj()->query($qu);
if(!$temp)
die($this->getConObj()->error);
return $temp;
}
}
// test
$obj = mySQL::getObj();
if($obj->makeQuery("SELECT * FROM users")->num_rows > 1)
echo "yes";
else
echo "no";
?>وإلى هنا والسلام عليكم وإلا لقاء أخر بإذن الله تعالى