PHP 7 এর সাথে হাই, হ্যালো!

Image Credit: PHPgang blog

আপনি যদি PHP ডেভেলপার হয়ে থাকেন তাহলে ধরে নেয়া যায় আপনি ইতোমধ্যেই PHP 7 এর সাথে পরিচিত। সে হিসেবে PHP 7 এর চমৎকার ফীচারগুলোর সাথেও আপনার পরিচয় হয়ে যাওয়ার কথা। তবে আমার ধারণা, পরিচয় থাকলেও বেশীরভাগ PHP ডেভেলপারই PHP 5 এ কোড করার অভ্যাসের কারণেই হোক বা ফ্রেমওয়ার্ক ডিপেন্ডেন্ড কাজ করার কারণেই হোক PHP 7 এর ফীচারগুলো কোডে খুব একটা ইমপ্লিমেন্ট করেন না। জায়গায় দাঁড়িয়ে আওয়াজ দেন, ঠিক কিনা?
সে যাই হোক, PHP 7 এর ফিচারগুলো যেহেতু বেশ কিছু সুবিধার কথা বিবেচনা করেই আনা হয়েছে, আস্তে আস্তে এগুলোর বহুল ব্যবহার শুরু করে দেয়া উচিত। আমি নিজেও নিয়মিত PHP তে কাজ করার পরও এতদিন খুব একটা PHP 7 এর ফিচার ব্যবহার করিনি। সেদিন Nahid Bin Azhar ভাইয়ের কল্যাণে PacktPublication থেকে প্রকাশিত ‘Mastering PHP 7’ এর PDF ফ্রি পাওয়ার পর মনে হলো PHP 7 এর ফীচারগুলোর সাথে ভালোভাবে পরিচিত হওয়া দরকার। সেই উদ্দেশ্যেই বইটা পড়া শুরু করেছি, আর সেখান থেকেই নিজের মত করে ফীচারগুলোর সাথে আপনাদের আরেকটু ভালো করে পরিচিত করার এই চেষ্টা।

কথা কম, কাজ বেশী। চলেন শুরু করা যাক।

Scalar Type Declaration/Hinting

আপনি যদি ‘Dynamically Typed Language’ কি সেটা নাও জানেন, এটা তো নিশ্চয়ই জানেন যে, PHP তে আমরা কখনো কোন ভ্যারিয়েবলের আগে সেটার টাইপ, যেমনঃ integer, boolean, string এগুলো বলে দেইনা। অন্যান্য অনেক ল্যাঙ্গুয়েজেই এটা বলে দিতে হয়। যেমন C তে ভ্যারিয়েবল কিভাবে ডিক্লেয়ার করে জানেন নিশ্চয়ই।

int a = 10; 

কিন্তু PHP তে আমরা কিভাবে করি?

$a = 10; 

PHP ডাটা দেখে নিজেই বুঝে নেয় ভ্যারিয়েবলের টাইপ কি হবে। মজা না? দেখে মনে হচ্ছে না যে এটাই তো জোস? ল্যাঙ্গুয়েজ নিজেই যদি বুঝে নিতে পারে তাহলে আমার আর কষ্ট করে টাইপ বলে লাভ কি? উঁহু, এত সহজে বিচার করে দেয়া যাবে না। PHP, Javascript এর মত ডাইন্যামিক ল্যাঙ্গুয়েজগুলো (যারা রান টাইমে ভ্যালু দেখে ভ্যারিয়েবলের ডাটা টাইপ বুঝে নেয়) কোড করা সহজ করে দেয় ঠিকই কিন্তু পারফরমেন্সের দিক থেকে Statically Typed Language (যাদের ভ্যারিয়েবলের ডাটা টাইপ আগেই বলে দিতে হয়) গুলোকে টেক্কা দিতে পারে না। কারণ ডাটা টাইপ রানটাইমে ঠিক করে নিতে হয় বলে কম্পাইলার/ইন্টারপ্রেটারের কাজ বাড়ে। আর টাইপ আগে থেকে জানা থাকলে মেমোরি অপটিমাইজ করা কম্পাইলার/ইন্টারপ্রেটারের জন্য তুলনামূলক সহজ হয়। এছাড়াও রানটাইমে ডাটা সেইফটি নিশ্চিত করা যায় টাইপ জানা থাকলে। ডাইন্যামিক ল্যাঙ্গুয়েজগুলোতে সেটা সম্ভব হয় না।
PHP তে যদিও আগে টাইপ ডিক্লেয়ার করার কোন সুযোগ নেই, কিন্তু PHP 5 এ ফাংশনের প্যারামিটারের জন্য কিছু টাইপ বলে দেয়ার সুবিধা যুক্ত করা হয়েছিলো। টাইপগুলোর মধ্যে ছিল class, interface, array এবং callable. এতে করে ফাংশনের প্যারামিটারে ঠিক টাইপের অবজেক্ট, এ্যারে বা callable টাইপের ডাটা পাঠানো হচ্ছে কিনা সেটা নিশ্চিত করা যেত।

function transform(User $user, callable $transformer) {
...
}

এখানে যদি উপরের ফাংশনে প্রথম প্যারামিটারে User class এর অবজেক্ট এবং দ্বিতীয় প্যারামিটারে কোন callable টাইপের অবজেক্ট, যেমন callback function, না দেয়া হয় তাহলে PHP কান্নাকাটি শুরু করবে এবং Error দেখাবে। করবে না কেন? আপনি উল্টাপাল্টা ডাটা পাস করে দিবেন আমার ফাংশনে আর ফাংশনের মাথা খারাপ হয়ে যাবে, শেষে ঠিক মত কাজ করবে না, আবার বলবেন আমার ফাংশনে ভুল আছে। ফাইজলামী নাকি? এ কারণেই এটা খুব জোস একটা জিনিস। এতে করে নিশ্চিত করা যায় যে, ফাংশনে সঠিক টাইপের ডাটা পাস করা হয়েছে। এই ফীচারটা না থাকলে ডাটা টাইপ নিশ্চিত করার জন্য কষ্ট করে আবার ফাংশনের ভেতরে টাইপ চেক করে নেয়া লাগতো নীচের মত করে।

function transform($user, $transfomer) {
if (! ($user instanceof User && is_callable($transformer)) {
throw new Exception();
}
...
}

Type hinting সুবিধার কারণে এই কষ্টটুকু এখন আর করা লাগে না।

PHP 7 আরেকটু আগ বাড়িয়ে যুক্ত করে দিয়েছে Scalar বা Primitive টাইপের Type hinting সুবিধা। তারমানে এখন আপনি ফাংশনের প্যারামিটারে int, float, bool, string এই টাইপগুলোর জন্যও চেক করতে পারবেন। মজা না?

তবে এখানে একটা খেয়াল রাখার মত বিষয় হচ্ছে, Scalar Type hinting সুবিধা কাজে লাগানোর জন্য আপনি কেবলমাত্র ফাংশনের প্যারামিটারের সাথে Scalar Type টা বলে দিলেই হবে না। সেক্ষেত্রে PHP নিজে নিজে পাস করা ডাটাকে আপনার দেয়া টাইপে কনভার্ট করে ফেলার চেষ্টা করবে আর না পারলে আস্তে করে ফেইল করে বসে থাকবে। কোন আওয়াজ করবে না। তাহলে তো লাভ হলো না, তাই না? কারণ আমরা চাই টাইপ না মিললে PHP যাতে সাউন্ড করে, Error খায়। এজন্য একটা কাজ করতে হবে। আর তা হলো আপনার ফাইলের একেবারে শুরুতে declare(strict_types=1); এই লাইনটা বলে দিতে হবে। তাহলে PHP স্ট্রিক্টলি টাইপ চেক করবে। আরেকটা জিনিস এখানে মনে রাখতে হবে যে, এই লাইনটা আপনি যেই ফাইলে এড করবেন এটা কেবলমাত্র সেই ফাইলেই স্ট্রিক্ট চেকিং করবে, অন্য কোন ফাইলে না। এমনকি আপনি এই ফাইলে অন্য কোন ফাইল ইনক্লুড করলে সেগুলোতেও না। আলাদা আলাদা করে ফাইলগুলোতে এই লাইনটা বলে দিতে হবে। একইসাথে এটাও মনে রাখা জরুরী যে, strict মূলত চেক করবে যেখান থেকে আপনি ফাংশনটা কল করছেন সেখান থেকে, যেখানে ফাংশনটা ডিফাইন করেছেন সেখান থেকে না। তাই ফাংশন কলের ফাইলটাতে যেন strict এর লাইনটা থাকে সেটা খেয়াল রাখতে হবে।
মনে হতে পারে যে, declare(strict_types=1); এক জায়গায় ইনক্লুড করলেই কাজ হবে, সেটা করা হলো না কেন? এখানে মূল ব্যাপারটা হচ্ছে Backward Compatibility মেইন্টেইন করা। এমন হতে পারে আপনি আপনার প্রজেক্টে এই স্ট্রিক্ট চেকিং করতে চাচ্ছেন, অথচ আপনার প্রজেক্টে ইনক্লুড করা লাইব্রেরী, প্যাকেজগুলো হয়তো এই স্ট্রিক্ট চেকিং এর জন্য রেডি না। তাহলে আপনি এক জায়গায় Strict mode অন করে দিলে তো ঝামেলা হয়ে যাবে। এই জন্য সুবিধা দেয়া হয়েছে যাতে করে আপনি incrementally আপনার পুরনো কোন কোডবেজেও এটা ব্যবহার শুরু করতে পারেন।

চলুন, টুক করে একটা কোড এক্সাম্পল দেখে নেই।

<?php 
declare(strict_types=1);
function add(int $a, int $b) {
return $a + $b;
}
echo add(1, 2); // shows 3
echo add(1, '2'); //shows TypeError Exception

অনেকের মনে হতে পারে, এই রকম Type hinting এর জন্যে কোডে এরর খেলে তো বিপদ! যাকে কিনা বলে ‘কোড একেবারে গ্যাঁড়ায় যাবে’। আসলে এই এরর খাওয়াটা তুলনামূলকভাবে ভালো। কারণ, আপনি যদি টাইপ বলে না দেন, তাহলে কখনো কখনো ভুল টাইপের ডাটা ফাংশনে পাস হলে PHP এর automatic type conversion এর কারণে এমন কিছু ভুল হবে যেগুলো ট্র্যাক করে বের করাটা খুবই কষ্টকর হবে।

কিন্তু যদি ভুল ডাটা পাসের কারণে কোন এরর হয় তাহলে আপনি সহজেই এরর দেখে বের করে ফেলতে পারবেন কোথায় এবং কেন এরর হয়েছে, এবং সাথে সাথে প্রয়োজনমত সেটা ফিক্স করে ফেলতে পারবেন।

Return Type Declaration

ফাংশনের প্যারামিটারে যেমন বলে দেয়া যাবে কি টাইপের ডাটা ফাংশন রিসিভ করবে, একইসাথে এটাও বলে দেয়া যাবে যে ফাংশনটি কি টাইপের ডাটা রিটার্ন করবে। এটাকে বলা হচ্ছে ‘Return Type Declaration’.

কোডে দেখে নেই কেমন হবে সেটা।

function sum(float $a, float $b) : float {
...
}

ফাংশন ডিক্লারেশনের পরে একটা কোলন (:) দিয়ে তারপরে রিটার্ন টাইপ বলে দিতে হবে।ফাংশন আর্গুমেন্টে যেই টাইপগুলো ব্যবহার করা যাবে আগে বলে এসেছি, যেমন class, array, int, float, bool, string — সেগুলো সবই রিটার্ন টাইপেও একইরকমভাবে ব্যবহার করা যাবে।
মজার ব্যাপার হচ্ছে OOP এর কনসেপ্ট অনুযায়ী এই ধরনের টাইপ ডিক্লারেশনের ক্ষেত্রে Super Type ও সুন্দরমত কাজ করবে। যেমনঃ

class A { }
class B extends A { }
function getObject() : A {
return new B();
}

এই কোড সুন্দরভাবেই কাজ করবে। কারণ আমরা রিটার্ন টাইপে প্যারেন্ট ক্লাসের (Super Type) কথা বলে এসেছি এবং রিটার্ন করার সময় Child ক্লাসের ইন্সট্যান্স রিটার্ন করছি।

এখন এমন যদি হয় যে, বিভিন্ন ধরনের কন্ডিশনের উপর ভিত্তি করে আপনার ফাংশনটি ভিন্ন ভিন্ন টাইপের ডাটা রিটার্ন করতে পারে। তখন কি করবেন? এখানে প্রথম কথা হচ্ছে, একটা ফাংশন থেকে ভিন্ন ভিন্ন টাইপের ডাটা রিটার্ন করাটা good practice না। কারণ সেক্ষেত্রে আপনার ফাংশনকে যারা ব্যবহার করবে তাঁদের মাল্টিপল টাইপের ডাটা রিটার্ন হতে পারে সেটা মাথায় রেখে কোড করতে হবে, এবং কোনো কোনো ক্ষেত্রে এটা খেয়াল না করার কারণে কোডে bug থেকে যাবে। এ কারণে একটা ফাংশন থেকে যদি মাল্টিপল টাইপের ডাটা রিটার্ন করার মত অবস্থা হয়, তাহলে খুব সম্ভবত আপনার ফাংশনটা ‘single responsibility principle’ ভায়োলেট করছে এবং এক্ষেত্রে এটিকে ভেঙ্গে মাল্টিপল ফাংশনে পরিণত করতে হবে। তবে আপনার ফাংশনে এমন হতে পারে যে রিটার্ন করার মত কিছু নেই, সেক্ষেত্রে আপনার রিটার্ন টাইপ যদি string হয় তাহলে আপনি empty string (‘’) রিটার্ন করতে পারেন। যদি রিটার্ন টাইপ array হয় তাহলে empty array রিটার্ন করতে পারেন। তা না হলে null রিটার্ন করতে পারেন। (যদিও আমরা উপরে যেভাবে রিটার্ন টাইপ ডিক্লেয়ার করে এসেছি সেভাবে ডিক্লেয়ার করলে ফাংশন থেকে null রিটার্ন করলেও এরর খাবে। কিভাবে null রিটার্ন করা যাবে সেটা নিয়ে একটু পরেই আলোচনা করছি।)
অবশ্য একটা ফাংশনের জন্য মাল্টিপল রিটার্ন টাইপ দেয়া যায় কিনা সেটা নিয়ে বিস্তর তর্ক বিতর্ক চলছে এবং বিভিন্ন প্রপোজালও সাবমিট হয়েছে RFC হিসেবে। যার কোনোটাই এখন পর্যন্ত গৃহীত হয়নি। সুতরাং আপাতত আমাদের যেই অপশনগুলি হাতে আছে সেগুলো দিয়েই কাজ সারতে হবে।

এখানে একটু ছোট্ট করে জানিয়ে রাখি যে, PHP 7.2 তে আর্গুমেন্ট এবং রিটার্ন টাইপে ব্যবহারের জন্য object নামে নতুন একটা টাইপ যুক্ত হয়েছে। তারমানে আপনি যদি Type hint করে রাখেন যে আপনার ফাংশনটি প্যারামিটার হিসেবে object রিসিভ করে কিংবা রিটার্ন করে object তাহলে সেটি যে কোন টাইপের অবজেক্টের জন্যই ভ্যালিড হবে। যে কোন টাইপের object মানে কি? PHP এর is_object()মেথডে পাস করলে true রিটার্ন করবে এমন যে কোন কিছুকেই আপনি object টাইপে পাঠাতে পারবেন। অর্থাৎ আপনার টাইপ যদি দেয়া থাকে object আর আপনি সেখানে পাস করেন string কিংবা null কিংবা এমন কিছু যেটা কিনা PHP এর হিসেবে object না তাহলেই আপনি এরর খাবেন।

Nullable Types

মনে করুন, আপনি নীচের মত একটা ফাংশন লিখলেনঃ

function welcome(string $message) {
echo $message;
}

এখন strict_types অন করা থাকলে আপনি এই ফাংশনে welcome() কিংবা welcome(null) দিয়ে কল করতে পারবেন না। কারণ প্যারামিটার বলছে যে ফাংশনটা একটা string এক্সপেক্ট করে। তো আপনি করলেন কি, ফাংশনের প্যারামিটারটার জন্য default ভ্যালু দিয়ে দিলেন null, নীচের মত করে।

function welcome(string $message = null)

এখন, ফাংশনটাকে welcome() এবং welcome(null) উভয়ভাবেই কল করা যায়, কোন এরর দেয় না। ওয়েট! কিন্তু welcome() কল দিলে তো কাজ করা উচিত না। কারণ, আপনার প্যারামিটারটা তো অপশনাল না। আপনি তো চাচ্ছিলেন যে প্যারামিটারটা required ই থাকবে, কিন্তু null দিলেও সেটা কাজ করবে। অর্থাৎ আপনার $message প্যারামিটারটা হবে nullable.
PHP 7.0 পর্যন্ত এই ঝামেলাটা ছিল যে আপনি যদি কোন প্যারামিটারের জন্য null ভ্যালুকেও রিসিভ করতে চান, তাহলে সেটাকে default ভ্যালু null বসিয়ে দেয়া, যেটা একইসাথে সেটাকে optional ও বানিয়ে ফেলে। সেক্ষেত্রে আপনাকে আবার ফাংশনের ভেতরে চেক করা লাগতো যে, যেই ভ্যালুটা রিসিভ হয়েছে সেটা কি null কিনা। যদি null হয় তাহলে আপনার আসলে বোঝার কোন উপায় নেই সেটা কি ফাংশন কল করার সময় ignore করা হয়েছে নাকি আসলে null ই পাঠানো হয়েছে।

এই সমস্যা সমাধানের জন্য PHP 7.1 এ একটা নতুন ফিচার যুক্ত করা হয়েছে। সেটা হলো প্যারামিটারের টাইপের আগে একটা কোয়েশ্চেন মার্ক (?) বসিয়ে দেয়া। যেটা নির্দেশ করবে যে আপনার এই প্যারামিটারটা nullable, অর্থাৎ null ভ্যালু দিলেও সে সেটা রিসিভ করবে, তবে কল করার সময় এই প্যারামিটারটা null হলেও দিতে হবে। মানে প্যারামিটারটা ম্যান্ডেটরিই থাকবে।

একটা উদাহরণ দেখিঃ

function welcome(?string $message) {
echo $message;
}
welcome(); //invalid
welcome(null); //valid

তবে আপনি যদি এখানেও default ভ্যালু হিসেবে null দিয়ে দেন, তাহলে কিন্তু প্যারামিটারটা আবার অপশনালই হয়ে যাবে।

function welcome(?string $message = null) {
echo $message;
}
welcome(); //valid
welcome(null); //valid

এক্ষেত্রে আসলে (?) মার্ক দেয়া আর না দেয়া একই কথা। তাই, আপনি যদি প্যারামিটারটাকে nullable করতে চান এবং একইসাথে required ও রাখতে চান তাহলে default ভ্যালু হিসেবে null দিবেন না।

Void return type

কখনো কখনো আপনার এমন ফাংশন লেখার দরকার পড়ে না যে, ঐ ফাংশন কোন ভ্যালু রিটার্ন করবে না? অর্থাৎ ফাংশনটা হয়তো কোন একটা কিছু প্রিন্ট করবে বা কিছু একটা কাজ করবে কিন্তু তাঁর কোন ভ্যালু রিটার্ন করার দরকার পড়বে না। এখন আমরা যদি রিটার্ন টাইপ স্ট্রিক্টলি বলে দিতে চাই, তাহলে সেটা কিভাবে সম্ভব? এটা মূলত সম্ভব হয়েছে PHP 7.1 থেকে। যেখানে void নামে নতুন একটা রিটার্ন টাইপ ব্যবহারের সুবিধা যুক্ত করা হয়েছে। তারমানে এখন আপনি চাইলে নীচের মত করে কোড লিখতে পারবেন।

function welcome(string $message) : void {
echo $message;
}

দেখেন যে, এখানে আমাদের কোন ভ্যালু রিটার্ন করা লাগবে না, তাই আমরা রিটার্ন টাইপ হিসেবে void বলে দিতে পারবো।

কোন ভ্যালু ছাড়া return; স্টেটমেন্ট ও কিন্তু return void ই বোঝায়, তাই আপনি চাইলে কোনো একটা কন্ডিশনের উপর ভিত্তি করে early return ও করতে পারবেন এ ধরনের ফাংশন থেকে।

function welcome(string $name = null) : void {
if(is_null($name)) {
return;
}

echo 'Hello, ' . $name;
}

আপনি হয়তো জানেন যে, ফাংশন যদি কোনো কিছু নির্দিষ্ট করে রিটার্ন না করে, তাহলে মূলত সেটা null ই রিটার্ন করে। মানে আপনি যদি void রিটার্ন করা কোনো একটা মেথড কলকে রিসিভ করেন তাহলে null পাবেন। তারমানে, আপনার ফাংশনের যদি রিটার্ন টাইপ স্ট্রিক্টলি void দেয়া থাকে তাহলে আপনি সেখান থেকে null রিটার্ন করলেও কাজ করবে?

function welcome(string $name = null) : void {
return null;
}

উপরের কোডটা কাজ করবে না, এরর দিবে। অদ্ভুত না? PHP নিজে নিজে null রিটার্ন করলে ঠিক আছে, আর আমি রিটার্ন করলে দোষ? এ কেমন বিচার!
আসলে ব্যাপারটা হচ্ছে, আমরা strict return type কেন দিচ্ছি এটা আগে বুঝতে হবে। আমরা যখন একটা ফাংশনে বলে দিচ্ছি যে এই ফাংশনটা void রিটার্ন করবে তারমানে আমরা নিশ্চিত করছি এই ফাংশন যা করার নিজেই করবে, এবং কোনো ডাটা রিটার্ন করা এর উদ্দেশ্য না। বা কোন ডাটা রিটার্ন করলেও সেটা আসলে গুরুত্বপূর্ণ না। কিন্তু আমরা যখন নিজেরা explicitly একটা ভ্যালু, যেটা হতে পারে null, রিটার্ন করছি তখন কিন্তু এই ভ্যালুটা গুরুত্বপূর্ণ। কারণ এই null ভ্যালু রিসিভ করে হয়তো যে ফাংশনটাকে কল করেছে সে কোনো একটা ডিসিশন নেবে। এই কারণেই ফাংশনের রিটার্ন টাইপ void দিলে পরে, সেটা থেকে যে কোনো ধরনের ভ্যালু রিটার্ন করার চেষ্টাকে PHP এরর বলবে।

টাইপ নিয়ে এতকিছু বলার পরে একটা বিষয় আবার বলে নেয়া জরুরী। যদিও ফাংশন আর্গুমেন্টে এবং রিটার্ন টাইপে নির্দিষ্ট করে টাইপ বলে দেয়ার সুযোগ PHP দিচ্ছে, তাতে করে কিন্তু PHP এর Dynamic Type চরিত্রের কোনো পরিবর্তন হয়নি। মানে আপনি এখন চাইলেই কোনো একটা ভ্যারিয়েবল ডিক্লেয়ার করার সময় সেটার টাইপ বলে দিতে পারবেন না (অন্তত এখনও না, শোনা যাচ্ছে যে PHP 7.4 হয়তো এরকম ফিচার যুক্ত হতে পারে।)। যেমন:

int $a = 10;

ফাংশনে এই টাইপ এর ব্যাপারটা যুক্ত করে আসলে লাভ কি হলো তাহলে? লাভ যেটা হলো তা হচ্ছে, ফাংশনগুলোর ডেফিনেশন দেখেই সেটা কি ধরনের ডাটা নিয়ে কাজ করছে, কি টাইপের ডাটা রিটার্ন করছে সেটা নিশ্চিত করা এবং ডাটার টাইপ সেইফটি নিশ্চিত করা। যাতে করে টেস্টিং করাও সহজ হয়, আর automatic type conversion এর কারণে হওয়া গতানুগতিক প্রব্লেমগুলো থেকে মুক্তি পাওয়া যায়। এটা যতটা না ‘টাইপ যুক্ত করে PHP কে জাতে তোলার চেষ্টা ’ তাঁর চাইতে অনেক বেশী PHP ডেভেলপারদের লাইফ আরো সহজ করা এবং PHP দিয়ে ডেভেলপ করা এপ্লিকেশনগুলোর কাজ আরও সুসংহত করার প্রচেষ্টা।

Null Coalesce Operator (??)

PHP তে যেহেতু আলাদা করে আগে ভ্যারিয়েবল ডিক্লেয়ার করে নিয়ে কাজ করতে হয় না, তাই আমাদের অহরহ চেক করতে হয় যে একটা ভ্যারিয়েবল exist করে কিনা, কিংবা এটা null কিনা। তারমানে আপনাকে হয়তো প্রায়শই এ রকম কোড লিখতে হয়ঃ

$name = (isset($firstName) && !empty($firstName)) ? $firstName : 'Guest';

অর্থাৎ যদি $firstName ভ্যারিয়েবলটা সেট করা হয়ে থাকে এবং সেটা null না হয় তাহলে সেটা ব্যবহার করবেন, না হলে default কোনো একটা ভ্যালু ব্যবহার করবেন। এই কাজটাই PHP 7 এর Null Coalesce Operator (??) দিয়ে করা এখন অনেক সহজ।

$name = $firstName ?? 'Guest';

মজা না?
আরো মজার ব্যাপার হচ্ছে যেহেতু এই অপারেটর বামদিক থেকে দেখতে থাকে কোন ভ্যালুটা not null এবং সেট করা আছে, সেটাই রিটার্ন করে, এ কারণে পর পর একাধিক ভ্যালুর জন্যও এক লাইনেই এরকম চেকিং করা যায়ঃ

$name = $firstName ?? $userName ?? $placeholder ?? 'Guest';

এখানে বাম থেকে যেই ভ্যালুটাকেই সে প্রথম ভ্যালিড হিসেবে পাবে সেটাকেই রিটার্ন করবে।চমৎকার একটা ফিচার!

Spaceship Operator (<=>)

দুইটা String compare করার রেজাল্ট কি হতে পারে? হয় দুইটা সমান হবে, কিংবা যে কোনো একটা বড় হবে, অপরটা ছোট হবে। এই comparison এর জন্য যেই ফাংশনগুলো লেখা হয় কিংবা কোনো কোনো ল্যাঙ্গুয়েজে যেই ডিফল্ট মেথডগুলো দেয়া থাকে (যেমন C তে strcmp) সেখান থেকে [-1, 0, 1] এর যে কোনো একটা ভ্যালু রিটার্ন হতে পারে। সমান হলে 0, প্রথম ভ্যালুটা ছোট হলে -1, আর বড় হলে 1.

আমরা সাধারণভাবে যেই comparison গুলো করি ==, !=, >, < দিয়ে তাতে আমাদের রেজাল্ট true অথবা false হয়। সে হিসেবে String এর comparison টা ভিন্ন। আর এ কারণেই হয় এটা if-else দিয়ে করা লাগে, না হলে নীচের মত একটু জটিলভাবে ternary operator দিয়ে করা লাগে।

function compare($a, $b) {
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}

হ্যাঁ ভাই-সকল, বুঝে ফেলেছেন নিশ্চয়ই। এই কাজ টাকেই সহজ করার জন্য চলে এসেছে Spaceship Operator (<=>)

এটা ব্যবহার করলে আমাদের উপরের কোডের চেহারা হবে এখন এই রকমঃ

function compare($a, $b) {
return $a <=> $b;
}

অস্থির! তাই না?
operator এর চেহারা দেখেই হয়তো আন্দাজ করে নিতে পারছেন যে এটা আসলে প্রথমে (<) দিয়ে চেক করে $a ছোট কিনা $b থেকে। যদি হয়, তাহলে -1 রিটার্ন করে দেয়। যদি না হয়, তাহলে চেক করে (=) কিনা। যদি হয়, তাহলে 0 রিটার্ন করে দেয়। আর তা না হলে 1.

Anonymous Class

PHP 7 এর পূর্বে PHP তে Anonymous Class এর অস্তিত্ব ছিলো না। Anonymous Class মূলত সাধারণ Class এর মতই। এটাতে আপনি সাধারণ Class এর মতই constructor ব্যবহার করা, অন্য Class কে extend করা, interface কে implement করা, trait ব্যবহার করা — মোট কথা সব কাজই করতে পারবেন।
এটা কখন কাজে লাগবে? মূলত কখনো কখনো এমন হতে পারে যখন on-the-fly তে আপনাকে একটা Class এর অবজেক্ট বানাতে হবে এবং এই Class এর পরে আর আলাদা করে কোন কাজে লাগার চান্স নেই। তখন আপনি এরকম Anonymous Class দিয়ে কাজ সারতে পারবেন। Test লেখার সময় স্বল্প সময়ের জন্য throw-away object বানাতে এটা বেশী কাজে লাগবে।
যেমনঃ

interface Logger 
{
public function log(string $input);
}
class MyClassThatRequiresALogger
{
private $m_logger;
public function __construct(Logger $logger)
{
$this->m_logger = $logger;
}
public function run()
{
$this->m_logger->log("I did something!");
}
}
$logger = new class() implements Logger {
public function log(string $input) { print($input); }
};
$myClass = new MyClassThatRequiresALogger($logger);
$myClass->run(); // I did something!

যেহেতু Anonymous Class এর আলাদা করে কোন নাম দেয়া হয় না, তাই এটা instantiate করতে হয় new class দিয়ে।
এখানে একটা বিষয় জেনে রাখা ভালো, সেটা হলো Anonymous Class যদি অন্য কোনো একটা Class এর ভেতরে থাকে তাহলে সেটি Outer Class এর private, protected মেম্বারগুলোতে এক্সেস পায় না, যদিও মনে হতে পারে যে Anonymous Class টা তো ঐ ক্লাসের ভেতরেই আছে। একারণেই এটা আলাদা করে বলা যাতে মনে থাকে। নীচের কোডটিতে দেখিঃ

class User {
private $name;

public function __construct($name) {
$this->name = $name;
}

public function transform() {
// If we don't pass $this->name in the following line,
// the code will show error
return new class($this->name) {
private $name;
public function __construct($name) {
$this->name = $name;
}

public function getData() {
return strtolower($this->name);
}
};
}
}
$user = new User('John Doe');echo $user->transform()->getData();

এখানে Anonymous Class টি Outer Class এর $name ভ্যারিয়েবলে এক্সেস পাবে না, কারণ এটি private. এ কারণেই new class() এ প্যারামিটার হিসেবে এটিকে পাস করার দরকার হয়েছে। আশা করি বোঝা গেছে। নাহলে নিজেরাই একটু কোড এদিক ওদিক করে এক্সপেরিমেন্ট করে ভালো মতে বুঝে নিন।

আরো কিছু চমৎকার ফীচার PHP ৭ এ যুক্ত হয়েছে, যেগুলো খুব বেশী বিস্তারিত বলার দরকার নেই, সেগুলো সংক্ষেপে একটু বলে ফেলি, কেমন?

Constant arrays

PHP তে constant মূলত ২ ভাবে বানানো যায়।

  • define() মেথডের মাধ্যমে
  • Class এ const হিসেবে ভ্যারিয়েবল ডিক্লেয়ার করে

PHP ডেভেলপার হলে এটা আপনি অলরেডি জানার কথা।
আগে কেবলমাত্র Scalar type ডাটাই (যেমন string, int, float, bool) constant হিসেবে ডিক্লেয়ার করা যেত। PHP 5.6 থেকে Class এ const হিসেবে array ও ডিক্লেয়ার করা যায়। PHP 7 এ এসে এটি আরও এক ধাপ এগিয়ে define() এও array ডিক্লেয়ার করার সুযোগ যুক্ত করেছে। দুইটাই যখন একই রকম কাজ করে, তখন আর পার্থক্য রেখে লাভ কি, তাই না?
ব্যবহার করতে পারবেন এভাবেঃ

define('ANIMALS', [
'dog',
'cat',
'bird'
]);

Symmetric Array Destructuring

আগে array থেকে একাধিক ভ্যারিয়েবলে সরাসরি ডাটা নিতে হলে list() ব্যবহার করে নীচের মত করে করা লাগতো।

list($firstName, $lastName) = ["John", "Doe"];
echo $firstName; // "John"

PHP 7.1 থেকে এটা এখন আরও সহজ। এখন থেকে আর list() লাগবে না, []দিয়েই করা যাবে নীচের মত করে।

[$firstName, $lastName] = ["John", "Doe"];
echo $firstName; // "John"

ভ্যারিয়েবল swap করা এখন এক্কেবারে পানি-ভাত।

$a = 10;
$b = 20;
[$a, $b] = [$b, $a];
echo $a; // 20
echo $b; // 10

এমনকি foreach লুপেও এটা খুব সহজে ব্যবহার করা যাবে।

$data = [
[1, 'John'],
[2, 'Doe'],
];
foreach ($data as [$id, $name]) {
// logic here with $id and $name
}

Class constant visibility

আগে Class constant গুলোর জন্য আলাদা করে visibility modifier যেমনঃ public, protected, private দেয়া যেত না, বাই ডিফল্ট সবই public থাকতো। PHP 7.1 থেকে Class constant গুলোকে আলাদা করে public, protected, private হিসেবে বলে দিয়ে এক্সেস কন্ট্রোল করা যাবে।

class ConstDemo
{
const PUBLIC_CONST_A = 1; // no access modifier means 'public'
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}

Group use declarations

একই namespace থেকে Class, function বা constant গুলোকে এখন আলাদা আলাদা লাইনে import না করে এক লাইনেই group করে import করা যাবে। নীচের মত করেঃ

// Pre PHP 7 code
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;
// PHP 7+ code
use some\namespace\{ClassA, ClassB, ClassC as C};

Error Handling

PHP তে Error আর Exception দুটো আলাদা জিনিস। যদিও সাধারণ অর্থে দুটোই এরর। আমরা যখন কোডে try…catch ব্যবহার করি তখন try এর ভেতরের ব্লকে কোডে কোনো Exception হলে সেটা catch এ ধরা পড়ে এবং সেটা আমরা তখন নিজেদের মত করে হ্যান্ডেল করতে পারি। কিন্তু Error হলে সেটা catch এ ধরা পড়বে না বরং কোড ক্র্যাশ করে বসে থাকবে। এই ঝামেলা দূর করার জন্য PHP 7 এ Error Handling নতুন করে রি-ডিজাইন করা হয়েছে। এখন Error আর Exception দুটোই Throwable interface কে এক্সটেন্ড করে। তারমানে হচ্ছে, Error আর Exception দুটোরই প্যারেন্ট Throwable. এ কারণে আমরা এখন try…catch এ হওয়া Error কেও catch এ নীচের মত করে ধরে ফেলতে পারবো।

try {
//Something terrible happened
} catch(Throwable $e) {
echo "Crashed";
}

Multi catch exception handling

আমরা অনেক ক্ষেত্রেই হয়তো একটা সিঙ্গেল try ব্লকের সাথে একাধিক ভিন্ন ভিন্ন Exception এর জন্য আলাদা আলাদা catch ব্লক ব্যবহার করি। নীচের মতঃ

try {
// some code
} catch (FirstException $e) {
// handle first exception
} catch (SecondException $e) {
// handle second exception
} catch (Exception $e) {
// If all fails, handle a generic Exception here
}

এখন যদি এমন হয় যে, ভিন্ন ভিন্ন এক্সেপশনের জন্য আমরা একই রকমই হ্যান্ডেল কোড লিখি তাহলে আমাদের কোড duplicate করা লাগতেছে। সেটা যাতে আর না লাগে তার জন্য PHP 7.1 থেকে একই catch ব্লকে multiple Exception type ক্যাচ করার সুবিধা দেয়া হয়েছে, (|) অপারেটর ব্যবহার করে।

try {
// some code
} catch (FirstException | SecondException $e) {
// handle first and second exceptions
} catch (Exception $e) {
// If all fails, handle a generic Exception here
}

আছেন এখনো?? পড়তে পড়তে তো কাহিল হয়ে যাওয়ার কথা।

আমি কিন্তু কাহিল। লেখার শুরুতে ভুলেও ভাবি নাই যে এত বড় হয়ে যাবে। আবার একই টপিকস আলাদা আলাদা পর্ব করে লিখতে কেমন জানি লাগে। এক লেখায় থাকলে রেফারেন্স হিসেবে পরে বের করে চোখ বুলানো সহজ। তাই আর আলাদা করলাম না।
কিছু ফীচার নিয়ে ইচ্ছে করেই লিখলাম না, এমনিতেই লেখা অনেক বড় হয়ে গেছে। পরে সুযোগ হলে লিখবো আর না হলে নিজেরাই একটু চোখ বুলিয়ে নিতে পারবেন।
PHP 7 এর Generators রিলেটেড কিছু ফীচার নিয়ে লেখার ইচ্ছে ছিলো, পরে মনে হলো আলাদা করে Generators নিয়ে আগে লেখা দরকার। একারণে Generators এবং PHP 7 এ এর নতুন কি ফীচার এসেছে সেটা নিয়ে আলাদা করে একটা বিশদ লেখা লেখার ইচ্ছে আছে, পরে।
আজকে আপাতত এটুকুই।

Nahid Bin Azhar ভাইয়ের অমৃত বাণী দিয়ে শেষ করি।
“সময় থাকতে PHP শিখুন, আলোর পথে আসুন।” :P

--

--

Ahmed shamim hassan
প্রোগ্রামিং পাতা

Curious mind. Software Engineer. Polyglot programmer. Avid learner. Average person.