مجالات الأسماء في PHP

من موسوعة حسوب
< PHP

ما هي مجالات الأسماء؟ التعريف الواسع لمجالات الأسماء هو: طريقة لتغليف العناصر، ويمكن رؤية هذا الأمر كمفهوم مجرد في عدة أماكن. فعلى سبيل المثال، تعمل المجلدات في أنظمة التشغيل المختلفة على تجميع الملفات المرتبطة ببعضها البعض، وهي بمثابة مجال أسماء لتلك الملفات. لنأخذ المثال التالي للتوضيح: يمكن أن يكون الملف foo.txt موجودًا في كلا المجلدين ‎/home/greg و ‎/home/other ولكن لا يمكن لنسختين من هذا الملف أن تكونا في المجلد ذاته، وإضافة لذلك يتطلب الوصول إلى الملف foo.txt من خارج المجلد ‎/home/greg إضافة اسم المجلّد إلى اسم الملف باستخدام فاصل المجلدات للحصول على المسار ‎/home/greg/foo.txt. وهذا المبدأ مشابه لمفهوم مجالات الأسماء في عالم البرمجة.

الإصدار الوصف
7.0.0 Added Group use Declarations

صُمِّمت مجالات الأسماء في لغة PHP لحل مشكلتين تواجهان أصحاب مكتبات وتطبيقات PHP عند كتابة شيفرات تتضمن عناصر قابلة لإعادة الاستخدام مثل الأصناف والدوال:

  1. التضارب الحاصل بين الشيفرة المكتوبة وبين الأصناف والدوال والثوابت الداخلية الخاصة باللغة أو الخاصة بالطرف الثالث (أصحاب المكتبات والملحقات).
  2. القدرة على اختصار (أو تقصير) الأسماء الطويلة جدًّا والتي وضعت للتخفيف من المشكلة الأولى وزيادة مقروئية الشيفرة المصدرية.

تقدّم مجالات الأسماء في PHP وسيلة لتجميع الأصناف والواجهات والدوال والثوابت المرتبطة ببعضها البعض. إليك مثالًا عن صيغة مجالات الأسماء في PHP:

المثال 1: صيغة مجالات الأسماء

<?php
namespace my\name;
// راجع صفحة \تعريف مجالات الأسماء
class MyClass {}
function myfunction() {}
const MYCONST = 1;
$a = new MyClass;
$c = new \my\name\MyClass;
// راجع صفحة المجال العام
$a = strlen('hi');
// راجع استخدام مجالات الأسماء: اللجوء إلى الدوال والثوابت العامة
$d = namespace\MYCONST;
 // see "namespace operator and __NAMESPACE__
                    	// constant" section
$d = __NAMESPACE__ . '\MYCONST';
echo constant($d);
// راجع فصل "مجالات الأسماء وميزات اللغة الديناميكية"
?>

ملاحظة: أسماء مجالات الأسماء PHP و php والأسماء المركبة منها (مثل PHP\Classes) هي أسماء محجوزة في اللغة ولا يمكن استخدامها في الشيفرة.

تعريف مجالات الأسماء

صحيح أنّه يمكن لأي شيفرة صالحة أن تكون ضمن مجال الأسماء إلا أنّ أنواع الشيفرة التالي هي الوحيدة التي تتأثر بمجالات الأسماء: الأصناف (وبضمنها الأصناف المجردة [abstract classes] والسمات [traits]) والواجهات (interfaces) والدوال والثوابت.

تعرّف مجالات الأسماء باستخدام الكلمة المفتاحية namespace، ويجب التصريح عن مجال الأسماء في بداية الملف قبل أي شيفرة أخرى، باستثناء الكلمة المفتاحية declare.

المثال 2: التصريح عن مجال أسماء مفرد

<?php
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
?>

تسمح اللغة بالتصريح عن البنية declare فقط قبل التصريح عن مجال الأسماء وذلك لتعريف الترميز الذي يعتمد ملف الشيفرة المصدرية، ولا تسمح باستخدام أي شيفرة غير تابعة للغة أو حتّى المسافات البيضاء: المثال 3: التصريح عن مجال أسماء مفرد

<?php
namespace MyProject;
// ستطلق هذه الشيفرة خطأً، إذ يجب أن يكون التصريح عن مجال الاسم في بداية الشيفرة.
?>

بالإضافة إلى ذلك، وعلى العكس من بنى PHP الأخرى، فإنّ اللغة تسمح بتعريف نطاق الاسم نفسه في ملفات متعدّدة، ممّا يسمح بتقسيم محتويات نطاق الاسم عبر الملفات.

تعريف مجالات الأسماء الفرعية

كما هو الحال مع المجلدات والملفات فإنّ بالإمكان تحديد تسلسل هرمي لأسماء مجالات الأسماء؛ لذا يمكن تعريف مجالات أسماء فرعية:

المثال 4: التصريح عن مجال اسم مفرد مع تسلسل هرمي

<?php
namespace MyProject\Sub\Level;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
?>

ينشئ المثال السابق الثابت MyProject\Sub\Level\CONNECT_OK، والصنف MyProject\Sub\Level\Connection والدالة MyProject\Sub\Level\connect.

تعريف مجالات أسماء متعددة في نفس الملف

يمكن التصريح عن مجالات أسماء متعددة في الملف نفسه، وهناك صيغتان للقيام بذلك:

المثال 5: التصريح عن مجالات أسماء متعددة بصيغة الجمع البسيطة (simple combination syntax)

<?php
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
namespace AnotherProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
?>

لا ينصح باستخدام هذه الصيغة لجمع مجالات الأسماء في ملف واحد، بل ينصح باستخدام صيغة الأقواس البديلة. المثال 6: التصريح عن مجالات أسماء متعددة بواسطة صيغة الأقواس (bracketed syntax)

<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}
?>

ينصح بشدّة تجنّب دمج نطاقات أسماء متعددة في ملف مفرد، والحالة الشائعة هي دمج شيفرات PHP متعددة في الملف نفسه.

يسمح باستخدام صيغة الأقواس فقط في حال الرغبة في دمج شيفرة عامة ليست موجودة في مجال أسماء مع شيفرة أخرى موجودة ضمن مجال أسماء، ويجب إحاطة الشيفرة العامة بعبارة namespace دون تحديد اسم لهذا المجال:

المثال 7: التصريح عن مجالات أسماء متعددة مع شيفرة غير موجودة ضمن مجال أسماء

<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}
namespace {
 // الشيفرة العامة
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>

لا تسمح اللغة بوجود أي شيفرة خارج أقواس مجال الأسماء باستثناء عبارة declare. المثال 8: التصريح عن مجالات أسماء متعددة وشيفرة غير موجودة في مجال أسماء

<?php
declare(encoding='UTF-8');
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}
namespace {
 // شيفرة عامة
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>

استخدام مجالات الأسماء: الأساسيات

من الضروري قبل الخوض في موضوع استخدام مجالات الأسماء أن تستوعب الطريق التي تتبعها PHP في التعرف على العنصر الذي ينتمي إلى مجال أسماء معين والذي تطلبه الشيفرة الخاصة بك. يمكننا في هذا الصدد أن نقارن بين مجالات الأسماء وملفات النظام. هناك ثلاث طرائق للوصول إلى ملف معيّن ضمن ملفات النظام:

  1. الاسم النسبي للملف مثل foo.txt، والذي يُحلَّل إلى المسار currentdirectory/foo.txt حيث currentdirectory هو المجلد الحالي، فلو كان المجلّد الحالي هو ‎/home/foo، فإنّ اسم الملف يصبح ‎/home/foo/foo.txt
  2. المسار النسبي مثل subdirectory/foo.txt، والذي يُحلَّل إلى currentdirectory/subdirectory/foo.txt
  3. اسم الملف المطلق مثل ‎/main/foo.txt، والذي يُحلّل إلى ‎/main/foo.txt

يمكن تطبيق المبدأ ذاته على العناصر المنتمية إلى مجال أسماء معيّن في PHP، فعلى سبيل المثال، يمكن الإشارة إلى اسم الصنف بثلاثة طرق:

  1. الاسم غير المؤهّل (Unqualified name)، أو اسم الصنف غير المتضمن لسابقة مثل ‎$a = new foo();‎ أو ‎foo::staticmethod();‎. إن كان مجال الأسماء الحالي هو currentnamespace، فإنّ اسم الصنف يُحلّل إلى currentnamespace\foo. وإذا كانت الشيفرة عامة وغير منتمية إلى مجال أسماء فإنّ اسم الصنف يُحلّل إلى foo.تُحلّل أسماء الدوال والثوابت غير المؤهّلة إلى دوال وثوابت عامة إن لم تعرّف الدالة أو الثابت غير المنتمي إلى مجال أسماء. راجع استخدام مجالات الأسماء: اللجوء إلى الدوال أو الثوابت العامة للمزيد من المعلومات.
  2. الأسماء المؤهّلة (Qualified names)، أو اسم الصنف مع سابقة مثل ‎$a = new subnamespace\foo();‎  أو ‎subnamespace\foo::staticmethod();‎. إن كان اسم مجال الأسماء الحالي هو currentnamespace فإنّ اسم الصنف يحلّل إلى currentnamespace\subnamespace\foo وإن كانت الشيفرة عامة لا تنتمي إلى مجال أسماء فإن اسم الصنف يحلّل إلى subnamespace\foo.
  3. الاسم المؤهّل بالكامل (Fully qualified name)، أو اسم الصنف المسبوق بعامل السابقة العامة (global prefix operator) مثل: ‎$a = new\currentnamespace\foo();‎ أو ‎currentnamespace\foo::staticmethod();‎. يُحلّل اسم الصنف دائمًا في هذه الحالة إلى الاسم الحرفي المحدّد في الشيفرة كما هو currentnamespace\foo.

إليك مثالًا عن الأنواع الثلاثة في شيفرة حقيقية:

file1.php

<?php
namespace Foo\Bar\subnamespace;
const FOO = 1;
function foo() {}
class foo
{
    static function staticmethod() {}
}
?>
file2.php
<?php
namespace Foo\Bar;
include 'file1.php';
const FOO = 2;
function foo() {}
class foo
{
    static function staticmethod() {}
}
/* اسم غير مؤهّل */
foo(); // resolves to function Foo\Bar\foo
foo::staticmethod(); // resolves to class Foo\Bar\foo, method staticmethod
echo FOO; // resolves to constant Foo\Bar\FOO
/* اسم مؤهّل */
subnamespace\foo(); // resolves to function Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // resolves to class Foo\Bar\subnamespace\foo,
                                  // method staticmethod
echo subnamespace\FOO; // resolves to constant Foo\Bar\subnamespace\FOO
                                  
/* اسم مؤهّل بالكامل */
\Foo\Bar\foo(); // resolves to function Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // resolves to class Foo\Bar\foo, method staticmethod
echo \Foo\Bar\FOO; // resolves to constant Foo\Bar\FOO
?>

لاحظ أنّه لغرض الوصول إلى أي صنف أو دالة أو ثابت في النطاق العام global يمكن استخدام الاسم المؤهّل بالكامل مثل ‎\strlen()‎ أو ‎\Exception أو ‎\INI_ALL. المثال 9: الوصول إلى الأصناف والدوال والثوابت العامة ضمن مجال الأسماء

<?php
namespace Foo;
function strlen() {}
const INI_ALL = 3;
class Exception {}
$a = \strlen('hi'); // calls global function strlen
$b = \INI_ALL; // accesses global constant INI_ALL
$c = new \Exception('error'); // instantiates global class Exception
?>

مجالات الأسماء وخصائص اللغة الديناميكية

تتأثر طريقة استخدام PHP لمجالات الأسماء بطبيعتها الديناميكية كلغة برمجية؛ لهذا لو أردنا تحويل الشيفرة في المثال التالي إلى شيفرة ضمن مجال أسماء:

المثال 10: الوصول الديناميكي إلى العناصر

<?php
class classname
{
	function __construct()
	{
    	echo __METHOD__,"\n";
	}
}
function funcname()
{
	echo __FUNCTION__,"\n";
}
const constname = "global";
$a = 'classname';
$obj = new $a; // prints classname::__construct
$b = 'funcname';
$b(); // prints funcname
echo constant('constname'), "\n"; // prints global
?>

يجب استخدام الاسم المؤهّل بالكامل (اسم الصنف مع سابقة اسم المجال). لاحظ أنّه نظرًا لعدم وجود فرق بين الاسم المؤهّل (qualified) والاسم المؤهّل بالكامل (fully qualified) داخل اسم صنف أو اسم دالة أو اسم ثابت ديناميكي، فلا حاجة لاستخدام الخط المائل. المثال 11: الوصول الديناميكي لعناصر ضمن مجال أسماء

<?php
namespace namespacename;
class classname
{
	function __construct()
	{
    	echo __METHOD__,"\n";
	}
}
function funcname()
{
	echo __FUNCTION__,"\n";
}
const constname = "namespaced";
/* لاحظ أنّه في حال استخدام علامات الاقتباس المزدوجة يجب استخدام
 "\\namespacename\\classname" */
$a = '\namespacename\classname';
$obj = new $a; // prints namespacename\classname::__construct
$a = 'namespacename\classname';
$obj = new $a; // also prints namespacename\classname::__construct
$b = 'namespacename\funcname';
$b(); // prints namespacename\funcname
$b = '\namespacename\funcname';
$b(); // also prints namespacename\funcname
echo constant('\namespacename\constname'), "\n"; // prints namespaced
echo constant('namespacename\constname'), "\n"; // also prints namespaced
?>

تأكّد من قراءة الملاحظة حول تهريب أسماء مجالات الأسماء في السلاسل النصّية.

الكلمة المفتاحية namespace و الثابت __NAMESPACE__

تقدّم PHP طريقتين للوصول المجرّد إلى العناصر ضمن مجال الأسماء الحالي، وهما الثابت السحري __NAMESPACE__ والكلمة المفتاحية namespace.

تكون قيمة الثابت السحري __NMAESPACE__ سلسلة نصية تحتوي اسم مجال الأسماء الحالي، وبصورة عامة تكون هذه السلسلة النصية فارغة في حال كون الشيفرة لا تنتمي إلى أي مجال أسماء.

المثال 12: مثال على الثابت السحري __NAMESPACE__ وشيفرة تنتمي إلى مجال أسماء

<?php
namespace MyProject;
echo '"', __NAMESPACE__, '"'; // outputs "MyProject"
?>

المثال 13: مثال على الثابت السحري __NAMESPACE__ وشيفرة لا تنتمي إلى مجال أسماء

<?php
echo '"', __NAMESPACE__, '"'; // outputs ""
?>

يمكن الاستفادة من الثابت __NAMESPACE__ في بناء الأسماء بطريقة ديناميكية، فعلى سبيل المثال: المثال 14: استخدام __NAMESPACE__ لبناء الأسماء بصورة ديناميكية

<?php
namespace MyProject;
function get($classname)
{
    $a = __NAMESPACE__ . '\\' . $classname;
    return new $a;
}
?>

يمكن استخدام الكلمة المفتاحية namespace لطلب عنصر من مجال الأسماء الحالي أو مجال الأسماء الفرعي، ويمكن اعتبار هذه الكملة المفتاحية مشابهة للعامل self في الأصناف. المثال 15: استخدام العامل namespace داخل مجال أسماء

<?php
namespace MyProject;
use blah\blah as mine; // see "Using namespaces: Aliasing/Importing"
blah\mine(); // calls function MyProject\blah\mine()
namespace\blah\mine(); // calls function MyProject\blah\mine()
namespace\func(); // calls function MyProject\func()
namespace\sub\func(); // calls function MyProject\sub\func()
namespace\cname::method(); // calls static method "method" of class MyProject\cname
$a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
$b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
?>

المثال 16: استخدام العامل namespace مع شيفرة عامة

<?php
namespace\func(); // calls function func()
namespace\sub\func(); // calls function sub\func()
namespace\cname::method(); // calls static method "method" of class cname
$a = new namespace\sub\cname(); // instantiates object of class sub\cname
$b = namespace\CONSTANT; // assigns value of constant CONSTANT to $b
?>

استخدام مجالات الأسماء: التسمية بأسماء بديلة/الاستيراد

من الميزات المهمة في مجالات الأسماء هي القدرة على الإشارة إلى اسم خارجي مؤهّل بالكامل (external fully qualified name) باستخدام اسم بديل (alias)، والقدرة على استيراد مجالات الأسماء. هذه الميزة مشابهة للقدرة على إنشاء روابط رمزية للملفات (symbolic links) أو المجلّدات في الأنظمة المبنية على يونكس (unix-based filesystems).

تتضمّن جميع إصدارات PHP التي تدعم مجالات الأسماء ثلاثة أنواع من التسمية البديلة والاستيراد: التسمية لبديلة لاسم الصنف، والتسمية البديلة لاسم الواجهة، والتسمية البديلة لاسم مجال الأسماء. يتيح الإصدار 5.6 وما بعده من PHP التسمية البديلة أو استيراد أسماء الدوال والثوابت.

يمكن استخدام العامل use لإنشاء التسمية البديلة، والمثال التالي يوضّح الأنواع الخمسة للاستيراد:

المثال 17: استيراد/التسمية البديلة باستخدام العامل use

<?php
namespace foo;
use My\Full\Classname as Another;
// هذا مطابق لما يلي
// My\Full\NSname as NSname
use My\Full\NSname;
// استيراد صنف عام
use ArrayObject;
// استيراد دالة في الإصدار 5.6 وما بعده
use function My\Full\functionName;
// إعادة تسمية دالة في الإصدار 5.6 وما بعده
use function My\Full\functionName as func;
// استيراد ثابت (الإصدار 5.6 وما بعده)
use const My\Full\CONSTANT;
$obj = new namespace\Another; // instantiates object of class foo\Another
$obj = new Another; // instantiates object of class My\Full\Classname
NSname\subns\func(); // calls function My\Full\NSname\subns\func
$a = new ArrayObject(array(1)); // instantiates object of class ArrayObject
// without the "use ArrayObject" we would instantiate an object of class foo\ArrayObject
func(); // calls function My\Full\functionName
echo CONSTANT; // echoes the value of My\Full\CONSTANT
?>

لاحظ أنّه لا حاجة (ولا ينصح بذلك أيضًا) لاستخدام الخط المائل مع الأسماء المنتمية إلى نطاق أسماء معيّن (أسماء مؤهّلة بالكامل تتضمن الرمز الفاصل في مجالات الأسماء مثل Foo\Bar، مقارنة بالأسماء العامة والتي لا تتضمن هذا الرمز مثل FooBar)؛ إذ يجب أن تكون الأسماء المستوردة مؤهّلة بالكامل، ولن تعالج بالنسبة إلى مجال الأسماء الحالي.

تدعم PHP كذلك اختصارًا يمكن عن طريقه استخدام عبارات use متعدّدة في نفس السطر.

المثال 18: استيراد/التسمية البديلة باستخدام العامل use مع دمج عدة عبارات في سطر واحد

<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // instantiates object of class My\Full\Classname
NSname\subns\func(); // calls function My\Full\NSname\subns\func
?>

تنفّذ عملية الاستيراد في وقت التصريف؛ لذا فإنّها لا تؤثر على أسماء الأصناف أو الدوال أو الثوابت الديناميكية. المثال 19: الاستيراد والأسماء الديناميكية

<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // instantiates object of class My\Full\Classname
$a = 'Another';
$obj = new $a;      // instantiates object of class Another
?>

تؤثّر عملية الاستيراد على الأسماء غير المؤهلة والمؤهّلة فقط، أما الأسماء المؤهّلة بالكامل فتكون مطلقة ولا تتأثر بعملية الاستيراد. المثال 20: الاستيراد والأسماء المؤهّلة بالكامل

<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // instantiates object of class My\Full\Classname
$obj = new \Another; // instantiates object of class Another
$obj = new Another\thing; // instantiates object of class My\Full\Classname\thing
$obj = new \Another\thing; // instantiates object of class Another\thing
?>

قواعد النطاقات في عملية التصدير

يجب التصريح عن الكلمة المفتاحية use في نطاق الملف الخارجي (أي النطاق العام global) أو داخل تصريحات مجالات الأسماء؛ وذلك لأن عملية الاستيراد تتمّ في وقت التصريف وليس في وقت التشغيل؛ لذا لا يمكن تحديدها بنطاق. يعرض المثال التالي طريقة غير صحيحة لاستخدام الكلمة المفتاحية use:

المثال 21: طريقة استيراد غير صحيحة

<?php
namespace Languages;
function toGreenlandic()
{
    use Languages\Danish;
    // ...
}
?>

ملاحظة: تسري قواعد الاستيراد في كلّ ملف على حدة، وهذا يعني أنّ الملفات المضمّنة لن ترث قواعد الاستيراد الخاصّة بالملف الأب.

تجميع تصريحات use

في الإصدار 7.0 وما بعده من PHP أصبح بالإمكان تجميع الأصناف والدوال والثوابت المستوردة من مجال الأسماء ذاته في عبارة use واحدة.

<?php
// قبل الإصدار السابع
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;
use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;
use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;
// بعد الإصدار السابع
use some\namespace\{ClassA, ClassB, ClassC as C};
use function some\namespace\{fn_a, fn_b, fn_c};
use const some\namespace\{ConstA, ConstB, ConstC};

المجال العام

تضع اللغة جميع الأصناف والدوال في المجال العام في حال عدم تعريف أي مجال أسماء، أي كما كان الحال قبل دعم ميزة مجالات الأسماء في PHP. إن استخدام الرمز \ قبل اسم الصنف أو الدالة يعني أنّ ذلك الاسم مطلوب من المجال العام حتى لو استخدم ضمن مجال أسماء معين.

المثال 22: استخدام المجال العام

<?php
namespace A\B\C;
/* This function is A\B\C\fopen */
function fopen() { 
     /* ... */
     $f = \fopen(...); // call global fopen
     return $f;
} 
?>

استخدام مجالات الأسماء: اللجوء إلى الدوال/الثوابت العامة

عندما تلاقي اللغة اسمًا غير مؤهّل في اسم صنف أو دالة أو ثابت ما، فإنّها تحلل هذه الأسماء بأولويات مختلفة. تحلّل اللغة أسماء الأصناف إلى اسم مجال الأسماء الحالي، وللوصول إلى الأصناف الداخلية أو غير المنتمية إلى مجال أسماء يجب الإشارة إلى هذه الأصناف باسمها المؤهّل بالكامل كما في المثال التالي:

المثال 23: الوصول إلى الأصناف العامة داخل مجال الأسماء

المتغير ‎$a في المثال التالي هو كائن للصنف A\B\C\Exception أما المتغيّر ‎$b فهو كائن للصنف Exception، أما المتغيّر ‎$c فسيطلق خطأ من نوع fatal لأنّ الصنف A\B\C\ArrayObject غير موجود.

<?php
namespace A\B\C;
class Exception extends \Exception {}
$a = new Exception('hi');
$b = new \Exception('hi');
$c = new ArrayObject;
?>

أما بالنسبة للدوال والثوابت فإنّ PHP تلجأ إلى الدوال أو الثوابت العامة في حال عدم وجود الدالة أو الثابت غير المنتميين إلى مجال أسماء. المثال 24: اللجوء إلى الدوال/الثوابت داخل مجال الأسماء

<?php
namespace A\B\C;
const E_ERROR = 45;
function strlen($str)
{
    return \strlen($str) - 1;
}
echo E_ERROR, "\n"; // prints "45"
echo INI_ALL, "\n"; // prints "7" - falls back to global INI_ALL
echo strlen('hi'), "\n"; // prints "1"
if (is_array('hi')) { // prints "is not array"
    echo "is array\n";
} else {
    echo "is not array\n";
}
?>

قواعد تمييز الأسماء

فيما يلي بعض التعريفات المهمّة التي تمهّد لقواعد التمييز هذه:

تعريفات اسم مجال الأسماء

  • اسم غير مؤهّل (Unqualified name): هو المعرّف الذي لا يتضمن فاصل مجال الأسماء، مثل Foo.
  • اسم مؤهّل (Qualified name): هو المعرّف الذي يتضمّن فاصل مجال الأسماء، مثل Foo\Bar.
  • اسم مؤهّل بالكامل (Fully qualified name): هو المعرّف الذي يتضمّن فاصل مجال الأسماء والذي يبدأ بهذا الفاصل أيضًا، مثل ‎\Foo\Bar. مجال الأسماء ‎\Foo هو أيضًا اسم مؤهّل بالكامل.
  • الاسم النسبي (Relative name): هو المعرّف الذي يبدأ بمجال الأسماء، مثل namespace\Foo\Bar.

تعالج الأسماء تبعًا لقواعد التمييز التالية:

  1. تحلل الأسماء المؤهّلة بالكامل إلى الاسم من دون إضافة فاصل مجال الأسماء في البداية. تحلّل ‎\A\B مثلًا إلى A\B.
  2. الأسماء النسبية تحلّل دائمًا إلى الاسم ويستبدل مجال الأسماء بمجال الأسماء الحالي. إن كان الاسم موجودًا في مجال الأسماء العام، فإنّ المقطع namespace\ يحذف من الاسم. على سبيل المثال يحلّل namespace\A داخل مجال الأسماء X\Y إلى X\Y\A، ويحلّل نفس الاسم في مجال الأسماء العام إلى A.
  3. يترجم المقطع الأول من الأسماء المؤهّلة بالكامل حسب جدول استيراد class/name الحالي. على سبيل المثال: إن تم استيراد الاسم A\B\C على أنه C، فإن الاسم C\D\E سيترجم إلى A\B\C\D\E.
  4. في حال عدم تطبيق أي قاعدة استيراد على الأسماء المؤهّلة بالكامل فإنّ مجال الأسماء الحالي يضاف إلى بداية الاسم. فمثلًا، يحلّل الاسم C\D\E في مجال الأسماء A\B إلى A\B\C\D\E.
  5. يترجم الاسم غير المؤهّل بالاعتماد على جدول الاستيراد الحالي إلى نوع الرمز المقابل. وهذا يعني أن الأسماء الشبيهة بالأصناف ستترجم بالاعتماد على جدول استيراد class/namespace، وتترجم الدوال بالاعتماد على جدول استيراد الدالة والثوابت بالاعتماد على جدول استيراد الثابت. فمثلًا بعد استخدام A\B\C يحلّل التعبير new C()‎ إلى الاسم A\B\C()‎. كذلك الأمر بعد استخدام الدالة A\B\fn;‎ فإن التعبير fn()‎ سيحلّل إلى الاسم A\B\fn.
  6. إن كان الاسم غير المؤهّل يشير إلى رمز شبيه بالصنف وفي حال عدم تطبيق أي قاعدة استيراد، سيضاف إلى اسم المجال الحالي إلى بداية الاسم. فعلى سبيل المثال تحلّل العبارة new C()‎ داخل مجال الأسماء A\B إلى الاسم A\B\C.
  7. في حال عدم تطبيق أي قاعدة استيراد وكان الاسم غير المؤهل لدالة أو ثابت وكانت الشيفرة خارج المجال الام، فإن الاسم سيحلّل في وقت التشغيل. فلو افترضنا أن الشيفرة ضمن مجال الأسماء A\B فإن استدعاء الدالة foo()‎ سيحّلل إلى:
    1. سيجري البحث عن دالة من مجال الأسماء الحالي: A\B\foo()‎.
    2. سيجري البحث عن الدالة العامة foo()‎ واستدعائها.

المثال 25: مثال يوضح طريقة تحليل الأسماء

ليكن لدينا ملف PHP الذي يحتوي على ما يلي (قسّمناه إلى أقسام ليسهل شرحه):

<?php
namespace A;
use B\D, C\E as F;
?>

سنحاول أولًا استدعاء الدالة foo في مجال الأسماء A، ثم لن يُعثَر عليها وستُستدعى الدالة foo في المجال العام:

<?php
foo();
?>

استدعاء الدالة foo المُعرَّفة في المجال العام:

<?php
\foo();
?>

استدعاء الدالة foo المُعرَّفة في مجال الأسماء A/my:

<?php
my\foo(); 
?>

محاولة استدعاء الدالة F في مجال الأسماء A، ثم استدعاء الدالة العام F:

<?php
F();
?>

إنشاء كائن من الصنف B المُعرَّفة في مجال الأسماء A، وإن لم يُعثَر عليه فسيحاول المُفسِّر التحميل التلقائي للصنف A/B:

<?php
new B();
?>

إنشاء كائن من الصنف D المُعرَّفة في مجال الأسماء B (ألقِ نطرةً على تعبير import أعلاه)، وإن لم يُعثَر عليه فسيحاول المُفسِّر التحميل التلقائي للصنف B/D:

<?php
new D();
?>

إنشاء كائن من الصنف E المُعرَّف في مجال الأسماء C (أنظر مليًا إلى تعبير import)، وإن لم يُعثَر عليه فسيحاول المُفسِّر التحميل التلقائي للصنف C/E:

<?php
new F();
?>

إنشاء كائن من الصنف B المُعرَّف في المجال العام، وإن لم يكن موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف B:

<?php
new \B();
?>

إنشاء كائن من الصنف D المُعرَّف في المجال العام، وإن لم يكن موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف D:

<?php
new \D();
?>

إنشاء كائن من الصنف F المُعرَّف في المجال العام، وإن لم يكن موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف F:

<?php
new \F();
?>

استدعاء الدالة foo من مجال الأسماء A/B:

<?php
B\foo();
?>

استدعاء الدالة foo التابعة للصنف B المُعرَّف في مجال الأسماء A، وإذا لم الصنف A/B موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف A/B:

<?php
B::foo();
?>

استدعاء الدالة foo التابعة للصنف D المُعرَّف في مجال الأسماء B، وإذا لم الصنف B/D موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف B/D:

<?php
D::foo();
?>

استدعاء الدالة foo من مجال الأسماء B:

<?php
\B\foo();
?>

استدعاء الدالة foo من الصنف B المُعرَّف في المجال العام، وإن لم يكن الصنف B موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف B:

<?php
\B::foo();
?>

استدعاء الدالة foo التابعة للصنف B من مجال الأسماء A/A، وإذا لم يكن الصنف A/A/B موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف A/A/B:

<?php
A\B::foo();
?>

استدعاء الدالة foo التابعة للصنف B من مجال الأسماء A، وإذا لم يكن الصنف A/B موجودًا فسيحاول المُفسِّر التحميل التلقائي للصنف A/B:

<?php
\A\B::foo();
?>

الأسئلة المتداولة: أمور يجب عليك معرفتها حول مجالات الأسماء

تقسم هذه الأسئلة المتداولة إلى قسمين: الأسئلة الشائعة، وبعض الاستخدامات التي من المفيد استيعابها بشكل كامل.

أنا لا أستخدم مجالات الأسماء هل علي الاهتمام بهذا؟

لا. لن تؤثّر مجالات الأسماء على شيفرتك الحالية، أو شيفرة ستكتبها في المستقبل ولا تتضمن مجالات الأسماء. يمكنك كتابة الشيفرة التالية إن كنت ترغب بذلك:

المثال 26: الوصول إلى الأصناف العامة خارج مجال الأسماء

<?php
$a = new \stdClass;
?>

وهذا مكافئ لما يلي: المثال 27: الوصول إلى الأصناف العامة خارج مجال الأسماء

<?php
$a = new stdClass;
?>

كيف أستخدم الأصناف الداخلية أو العامة في مجال الأسماء؟

المثال 28: الوصول إلى الأصناف الداخلية في مجالات الأسماء

<?php
namespace foo;
$a = new \stdClass;
function test(\ArrayObject $typehintexample = null) {}
$a = \DirectoryIterator::CURRENT_AS_FILEINFO;
// توسيع صنف داخلي أو عام
class MyException extends \Exception {}
?>

كيف أستخدم الأصناف أو الدوال أو الثوابت التابعة لمجال أسماء معين داخل ذلك المجال؟

المثال 29: الوصول إلى الأصناف أو الدوال أو الثوابت الداخلية في مجالات الأسماء

<?php
namespace foo;
class MyClass {}
// استخدام صنف من مجال الأسماء الحالي كإشارة إلى النوع
function test(MyClass $typehintexample = null) {}
// طريقة أخرى لاستخدام الصنف من مجال الأسماء الحالي كإشارة إلى النوع
function test(\foo\MyClass $typehintexample = null) {}
// توسيع الصنف من مجال الأسماء الحالي
class Extended extends MyClass {}
// الوصول إلى دالة عامة
$a = \globalfunc();
// الوصول إلى ثابت عام
$b = \INI_ALL;
?>

كيف سيُحلّل اسم مثل ‎\my\name أو ‎\name؟

تحلّل الأسماء التي تبدأ بالرمز \ دائمًا إلى الهيئة ذاتها، لذا فإنّ الاسم ‎\my\name سيحلّل إلى my\name والاسم ‎\Exception سيحلّل إلى Exception.

المثال 30: الأسماء المؤهّلة بالكامل

<?php
namespace foo;
$a = new \my\name(); // instantiates "my\name" class
echo \strlen('hi'); // calls function "strlen"
$a = \INI_ALL; // $a is set to the value of constant "INI_ALL"
?>

كيف سيحلّل اسم مثل my\name؟

تحلّل الأسماء التي تحتوي على الرمز \ ولكنّها لا تبدأ به بطريقتين مختلفتين:

إن كانت هناك عبارة import تضع اسمًا بديلًا لـ my فإنّ الاسم البديل سيطبّق أيضًا على my في my\name.

في حال عدم وجود اسم بديل، يصبح اسم مجال الأسماء الحالي هو my\name.

المثال 31: الأسماء المؤهّلة

<?php
namespace foo;
use blah\blah as foo;
$a = new my\name(); // instantiates "foo\my\name" class
foo\bar::name(); // calls static method "name" in class "blah\blah\bar"
my\bar(); // calls function "foo\my\bar"
$a = my\BAR; // sets $a to the value of constant "foo\my\BAR"
?>

كيف سيحلّل اسم صنف غير مؤهّل مثل name؟

يمكن للأسماء التي لا تحتوي على الرمز \ مثل name أن تحلّل بطريقتين مختلفتين:

في حال وجود عبارة import تضع اسمًا بديلًا للاسم name فيستخدم الاسم البديل، وإلا فإنّ اسم مجال الأسماء الحالي يصبح name.

المثال 23: أسماء الأصناف غير المؤهّلة

<?php
namespace foo;
use blah\blah as foo;
$a = new name(); // instantiates "foo\name" class
foo::name(); // calls static method "name" in class "blah\blah"
?>

كيف سيحلّل اسم دالة أو ثابت غير مؤهّل مثل name؟

تحلّل أسماء الدوال والثوابت التي لا تحتوي على الرمز \ بطريقتين مختلفتين:

الأولى: يصبح اسم مجال الأسماء الحالي name.

الثانية: إن كان اسم الثابت أو الدالة غير موجود في مجال الأسماء الحالي، تلجأ اللغة إلى استخدام اسم عام للدالة أو الثابت في حال توفره.

المثال 33: أسماء دوال وثوابت غير مؤهّلة

<?php
namespace foo;
use blah\blah as foo;
const FOO = 1;
function my() {}
function foo() {}
function sort(&$a)
{
    \sort($a); // calls the global function "sort"
    $a = array_flip($a);
    return $a;
}
my(); // calls "foo\my"
$a = strlen('hi'); // calls global function "strlen" because "foo\strlen" does not exist
$arr = array(1,3,2);
$b = sort($arr); // calls function "foo\sort"
$c = foo(); // calls function "foo\foo" - import is not applied
$a = FOO; // sets $a to value of constant "foo\FOO" - import is not applied
$b = INI_ALL; // sets $b to value of global constant "INI_ALL"
?>

لا يمكن للأسماء المستوردة أن تتضارب مع الأصناف المعرّفة في نفس الملف

لا توجد أي مشكلة في الشيفرة التالية:

file1.php

<?php
namespace my\stuff;
class MyClass {}
?>
another.php
<?php
namespace another;
class thing {}
?>

file2.php

<?php
namespace my\stuff;
include 'file1.php';
include 'another.php';
use another\thing as MyClass;
$a = new MyClass; // instantiates class "thing" from namespace another
?>

لا يوجد تضارب في الأسماء مع أنّ الصنف MyClass موجود ضمن مجال الأسماء my\stuff؛ وذلك لأنّ الصنف MyClass معرّف في ملف منفصل. ولكن المثال التالي سيتسبّب في حدوث خطأ من نوع fatal ناجم عن تضارب في الأسماء؛ وذلك لأنّ الصنف MyClass قد عُرِّف في الملف نفسه مع عبارة use.

<?php
namespace my\stuff;
use another\thing as MyClass;
class MyClass {} // fatal error: MyClass conflicts with import statement
$a = new MyClass;
?>

لا يسمح بتداخل مجالات الأسماء

لا تسمح PHP بتداخل مجالات الأسماء:

<?php
namespace my\stuff {
    namespace nested {
        class foo {}
    }
}
?>

ولكن يمكن محاكاة عملية تداخل مجالات الأسماء بسهولة:

<?php
namespace my\stuff\nested {
    class foo {}
}
?>

قبل الإصدار 5.6 من PHP لا يمكن استيراد الدوال أو الثوابت باستخدام العبارة use

قبل الإصدار 5.6 من اللغة كانت مجالات الأسماء وأسماء الأصناف هي العناصر الوحيدة التي تتأثر بعبارة use. ولغرض تقصير اسم ثابت أو دالة طويل يجب استيراد مجال الأسماء الذي يحتويها.

<?php
namespace mine;
use ultra\long\ns\name;
$a = name\CONSTANT;
name\func();
?>

أصبح في الإمكان تعيين أسماء بديلة أو استيراد أسماء الدوال والثوابت في الإصدار 5.6 وما بعده من PHP.

يجب أن تهربّ الأسماء الديناميكية لمجالات الأسماء (المعرّفات المحاطة بعلامة اقتباس) رمز الخطّ المائل \

من الضروي جدًّا إدراك أنّه بسبب استخدام الرمز \ كحرف للتهريب ضمن السلاسل النصية، فإنّه يجب استخدام هذا الرمز مرتين داخل السلاسل النصية لتجنّب حدوث مشاكل غير متوقعة:

المثال 34: مخاطر استخدام أسماء غير منتمية لمجال أسماء داخل سلسلة نصّية محاطة بعلامة اقتباس مزدوجة

لاحظ أنّ الرمز ‎\n يعني سطرًا جديدًا داخل السلاسل النصية المحاطة بعلامة اقتباس مزدوجة.

<?php
$a = "dangerous\name";
$obj = new $a;
$a = 'not\at\all\dangerous';
// لا مشكلة هنا على الإطلاق
$obj = new $a;
?>

أما السلال النصّية المحاطة بعلامة اقتباس مفردة فلا مشكلة في استخدام الرمز \ فيها، ومع ذلك ينصح بعدم استخدام هذا الرمز في جميع أنواع السلاسل النصية.

تؤدي الإشارة إلى الثوابت غير المعرّفة باستخدام رمز الخطّ المائل \ إلى حدوث خطأ من نوع fatal

ستطلق اللغة أي ثابت غير معرّف وغير مؤهّل مثل FOO ملاحظة تخبرك بأنّ اللغة قد افترضت أن FOO هي قيمة الثابت. في حال لم تعثر اللغة على أي ثابت مؤهّل أو مؤهّل بالكامل ويتضمن الرمز \، فإنّها ستطلق خطأً من نوع fatal.

المثال 35: ثوابت غير معرّفة

<?php
namespace bar;
$a = FOO; // produces notice - undefined constants "FOO" assumed "FOO";
$a = \FOO; // fatal error, undefined namespace constant FOO
$a = Bar\FOO; // fatal error, undefined namespace constant bar\Bar\FOO
$a = \Bar\FOO; // fatal error, undefined namespace constant Bar\FOO
?>

لا يمكن تجاوز (إعادة تعريف) الثوابت الخاصة NULL و TRUE و FALSE و ZEND_THREAD_SAFE و ZEND_DEBUG_BUILD

ستؤدي أي محاولة لتعريف ثابت خاص باللغة ينتمي إلى مجال أسماء معين إلى حدوث خطأ من نوع fatal.

المثال 36: ثوابت غير معرّفة

<?php
namespace bar;
const NULL = 0; // fatal error;
const true = 'stupid'; // fatal error;
// إلخ...
?>

مصادر