آبجیکٹ ہائیڈریشن (Object hydration) ایک حل شدہ مسئلہ معلوم ہوتا ہے جب تک کہ آپ اسے پیمائش کے ذریعے نہ دیکھیں۔ آپ ڈیٹا بیس سے ایک رو (row) حاصل کرتے ہیں، اس ایرے کو ایک آبجیکٹ میں تبدیل کرتے ہیں، اور آگے بڑھ جاتے ہیں۔ ہم میں سے اکثر اسے محض پلمبنگ (plumbing) سمجھتے ہیں—جو نظر نہیں آتی، غیر دلچسپ ہے، اور یہ سمجھا جاتا ہے کہ یہ کافی تیز ہے۔ پھر ایک دن آپ کسی بیچ جاب (batch job) یا کیو ورکر (queue worker) کا پروفائل کرتے ہیں اور دیکھتے ہیں کہ CPU کا ایک حیران کن حصہ میپر (mapper) میں ضائع ہو رہا ہے۔ یہی وہ احساس ہے جس نے HydraType کو جنم دیا، ایک ایسا ہائیڈریٹر جو ایک ضدی اصول پر بنایا گیا ہے: ایک کلاس کو کبھی بھی ان فیچرز کی قیمت نہیں چکانی چاہیے جنہیں اس کی پراپرٹیز استعمال نہیں کرتیں۔
اگر کوئی فیلڈ ایک سادہ اسٹرنگ (string) ہے جسے کسی تبدیلی کی ضرورت نہیں ہے، تو تیار کردہ کوڈ ایسا ہونا چاہیے جیسے اسے کسی انسان نے خود ہاتھ سے لکھا ہو۔ براہ راست اسائنمنٹ۔ کوئی لوپس (loops) نہیں، کوئی پائپ لائنز (pipelines) نہیں، اور کوئی ریفلیکشن (reflection) نہیں۔
ہائیڈریشن کی اصل قیمت کیا ہے
پہلی نظر میں، ['name' => 'Alice', 'age' => 30] کو User آبجیکٹ میں تبدیل کرنا معمولی لگتا ہے۔ مشکل تب شروع ہوتی ہے جب آپ لچک (flexibility) چاہتے ہیں۔ زیادہ تر عام مقصد کے لیے استعمال ہونے والے ہائیڈریٹرز رن ٹائم پر کلاسز کا معائنہ کرنے کے لیے ریفلیکشن (reflection) پر انحصار کرتے ہیں۔ وہ میٹا ڈیٹا میپس (metadata maps) بناتے ہیں، ٹائپ کویرشن (type coercion) کا فیصلہ کرتے ہیں، اور نییسٹڈ آبجیکٹ گراف (nested object graphs) کو حل کرتے ہیں۔ انہیں پائپ لائنز بھی بہت پسند ہیں۔ ہر ویلیو میوٹیٹرز (mutators)، اسرشنز (assertions)، اور ٹرانسفارمرز (transformers) کے ایک سلسلے سے گزرتی ہے، چاہے وہ سلسلہ خالی ہی کیوں نہ ہو۔ لوپ کا ڈھانچہ خود بھی اضافی بوجھ (overhead) ڈالتا ہے۔
کچھ درجن ریکارڈز حاصل کریں تو آپ کو کبھی پتہ نہیں چلے گا۔ لیکن جدید PHP ایپلی کیشنز معمول کے مطابق ہزاروں کیو میسجز (queue messages) پروسیس کرتی ہیں، بڑے CSV سیٹس امپورٹ کرتی ہیں، یا Elasticsearch سے گہرے رزلٹ سیٹس کو ہائیڈریٹ کرتی ہیں۔ ان حالات میں، ایک ایسا میپر جو فی فیلڈ چند مائیکرو سیکنڈز بھی ضائع کرتا ہے، ایک حقیقی رکاوٹ (bottleneck) بن جاتا ہے۔ آٹھ فیلڈز والے ایک ہزار آبجیکٹس اس ٹیکس (tax) میں ضرب ہو جاتے ہیں جسے آپ محسوس کرتے ہیں۔
زیرو اوورہیڈ (Zero-Overhead) فلسفہ
HydraType کے پیچھے مرکزی خیال فیچر آئسولیشن (feature isolation) ہے۔ ٹائپ کنورژن (type conversion)، نییسٹڈ آبجیکٹ ہائیڈریشن، اور اسرشنز (assertions) سب کو سپورٹ کیا جاتا ہے، لیکن ان میں سے کوئی بھی لازمی نہیں ہے۔ اگر کسی پراپرٹی کو ایرے سے آبجیکٹ تک صرف ایک سیدھی کاپی کے علاوہ کسی چیز کی ضرورت نہیں ہے، تو تیار کردہ رائٹر (writer) میں بالکل وہی ہوتا ہے۔ کوئی مشترکہ پائپ لائن نہیں ہے جو ایک سادہ اسکیلر فیلڈ کو ان ویلیڈیٹرز کے پیچھے لائن میں انتظار کرنے پر مجبور کرے جن کی اسے ضرورت نہیں ہے۔
یہ جتنا سننے میں لگتا ہے اس سے کہیں زیادہ مشکل ہے۔ بہت سی لائبریریاں ہر فیلڈ کے لیے ایک ہی ایگزیکیوشن پاتھ (execution path) استعمال کرتی ہیں کیونکہ اس سے کوڈ بیس چھوٹا اور یکساں رہتا ہے۔ HydraType اس کے برعکس طریقہ اپناتا ہے۔ یہ ہر ٹارگٹ DTO کے لیے ایک منفرد PHP کلاس تیار کرتا ہے، اور صرف وہی آپریشنز جاری کرتا ہے جن کی اس مخصوص کلاس کو ضرورت ہوتی ہے۔ پیچیدگی کو آپشنل (opt-in) بنایا گیا ہے، نہ کہ ہر پراپرٹی پر لگنے والا ایک عمومی ٹیکس۔
رفتار کیسے حاصل ہوتی ہے
چار ٹھوس انتخاب HydraType کو عام ریفلیکشن پر مبنی ٹولز اور دیگر جنریٹڈ طریقوں سے الگ کرتے ہیں۔
1. کوڈ ایک بار جنریٹ کریں، ہمیشہ کے لیے چلائیں
HydraType آپ کی ٹارگٹ کلاس کا صرف ایک بار معائنہ کرتا ہے، پھر اس کی بالکل درست شکل کے مطابق ایک مخصوص PHP کلاس لکھتا ہے۔ اگر آپ کے DTO میں ایک پبلک اسٹرنگ $name، ایک انٹ $status، اور ایک پرائیویٹ DateTime $createdAt ہے، تو تیار کردہ رائٹر کو یہ سب پہلے سے معلوم ہوتا ہے۔ رن ٹائم کے دوران کوئی بار بار ریفلیکشن، کوئی اسٹرنگ پارسنگ، اور کوئی سست میٹا ڈیٹا بلڈنگ نہیں ہوتی۔ ایک بار جب OPCache تیار کردہ فائل کو کمپائل کر دیتا ہے، تو ہائیڈریٹر ہاتھ سے لکھے گئے PHP سے بالکل الگ نہیں رہتا۔
2. خالی پائپ لائنز کا خاتمہ
زیادہ تر ہائیڈریٹرز اپنے کام کو رن ٹائم پائپ لائن کے گرد ترتیب دیتے ہیں۔ وہ کال ایبلز (callables)—میوٹیٹرز، ویلیڈیٹرز، ٹرانسفارمرز—کا ایک ایرے رکھتے ہیں اور ہر ویلیو پر لوپ چلاتے ہیں۔ یہاں تک کہ جب وہ ایرے خالی ہوں، تب بھی foreach یا array_reduce چلتا ہے۔ HydraType اسے مکمل طور پر ختم کر دیتا ہے۔ کوڈ جنریشن کے دوران یہ چیک کرتا ہے کہ پراپرٹی کو اصل میں کس چیز کی ضرورت ہے۔ اگر آپریشن کی فہرست خالی ہے، تو یہ ایک سادہ اسائنمنٹ جاری کرتا ہے جیسے $object->name = $data['name'];۔ رن ٹائم کبھی بھی کسی چیز پر لوپ نہیں چلاتا، کیونکہ جنریٹر نے پہلے ہی سوچ بچار کر لی ہوتی ہے۔
3. ریفلیکشن رائٹس کو ختم کرنے کے لیے کلوزر اسکوپنگ (Closure Scoping) کا استعمال
پرائیویٹ پراپرٹیز بیرونی میپرز کے لیے ایک کلاسک سردرد ہیں۔ عام حل ReflectionProperty::setValue() ہے، لیکن یہ طریقہ براہ راست پراپرٹی تک رسائی کے مقابلے میں بھاری نقصان کا باعث بنتا ہے۔ HydraType Closure::bind کا استعمال کرتے ہوئے اس سے بچتا ہے۔ تیار کردہ رائٹر میں ٹارگٹ کلاس کے اسکوپ میں کلوزرز (closures) ہوتے ہیں، جو انہیں ریفلیکشن کے بغیر براہ راست پرائیویٹ اور پروٹیکٹڈ پراپرٹیز تک رسائی دیتے ہیں۔ بائنڈنگ کنسٹرکشن کے دوران ایک بار ہوتی ہے؛ اس کے بعد، کلوزر تقریباً نیٹیو اسپیڈ (near-native speed) پر چلتا ہے۔
4. رائٹر کا دوبارہ استعمال
کچھ لائبریریاں ہر اس آبجیکٹ کے لیے ایک نیا اسٹریٹیجی یا ریفلیکشن گراف بناتی ہیں جسے وہ ہائیڈریٹ کرتی ہیں۔ HydraType اپنا رائٹر ایک بار بناتا ہے، پھر ہزاروں آبجیکٹس کے لیے اسی انسٹنس کو دوبارہ استعمال کرتا ہے۔ فی آبجیکٹ لاگت پراپرٹی ویلیوز سیٹ کرنے اور رزلٹ واپس کرنے کی کم سے کم حد تک گر جاتی ہے۔
اعداد و شمار
On PHP 8.2, the difference is stark:
- HydraType: 267.9 ns
- Ocramius GeneratedHydrator: 307.9 ns
- Symfony PropertyNormalizer: 8,499.0 ns
- Valinor: 10,755.2 ns
Symfony PropertyNormalizer and Valinor are powerful tools, but they are generalists. PropertyNormalizer is part of a serializer component that handles format detection, normalizers, and deep object graphs. Valinor is rigorous about type trees and coercion. That power comes with cost. In this test, they were roughly thirty to forty times slower than HydraType. Even Ocramius GeneratedHydrator, which already uses code generation, sits slightly behind because of architectural differences around pipelines and property access.
Context matters. A single database query or an HTTP roundtrip dwarfs all of these numbers. If you are hydrating three objects after a query, chasing nanoseconds is a waste of energy. The gap becomes meaningful when you scale. A batch process hydrating fifty thousand records, or a queue consumer rebuilding objects from cache arrays, will feel the difference between 267 nanoseconds and ten microseconds. Multiply across fields and rows, and the faster mapper can shave seconds off a job without changing any business logic.
The Real Takeaway
The lesson here is not that every project needs a custom hydrator. It is that architecture choices compound. By generating code that includes only what you ask for, HydraType keeps plain properties fast while still offering an escape hatch for complex ones. Type conversion and nested objects exist when you need them, but they do not co-sign the performance check for every scalar string on the object.
Before you switch tools, profile. If hydration does not show up in your real-world traces, there is nothing to fix. But if you are pushing large datasets through generic mappers and watching CPU burn, remember that plain PHP assignment still exists. Sometimes the fastest code is simply the code you would have written yourself, generated once and then forgotten.
