یک گواهینامه تنها زمانی اهمیت دارد که مردم به آن اعتماد داشته باشند. وقتی یک یادگیرنده یک فایل PDF را به یک درخواست شغلی پیوست می‌کند، مدیر استخدام باید بتواند در عرض چند ثانیه اصالت آن را تأیید کند. Laravel ابزارهای لازم را در اختیار شما قرار می‌دهد تا خودتان این اعتماد را ایجاد کنید، بدون اینکه کنترل را به یک پلتفرم شخص ثالث بسپارید. شما می‌توانید برندینگ، داده‌ها و زنجیره تأیید را از ابتدا تا انتها در اختیار داشته باشید.

هدف ساده است: تولید یک PDF حرفه‌ای که هنگام چاپ ظاهر زیبایی داشته باشد، حاوی یک کد QR قابل اسکن برای تأیید فوری باشد و یک رکورد منحصربه‌فرد را ذخیره کند که قابل حدس زدن یا جعل نباشد. در اینجا نحوه انجام این کار آمده است.

شروع با لایه پایگاه داده

از IDهای خودافزا (auto-incrementing) برای گواهینامه‌ها استفاده نکنید. شناسه‌ای مانند 8473 به راحتی قابل حدس زدن است. کسی می‌تواند با پیمایش اعداد در صفحه تأیید شما، تمام مدارکی را که تا به حال صادر کرده‌اید استخراج (scrape) کند. در عوض، هنگام ایجاد رکورد گواهینامه، یک UUID تولید کنید.

در migration خود، UUID را به عنوان یک رشته (string) ذخیره کنید. وقتی کاربری دوره‌ای را به پایان می‌رساند، رویدادی (event) را اجرا کنید که یک مدل Certificate مرتبط با کاربر و دوره ایجاد کند. نقطه پایانی (endpoint) تأیید را کاملاً از هرگونه داشبورد احراز هویت‌شده جدا نگه دارید. یک مسیر عمومی مانند /verify/{uuid} باید رکورد را جستجو کرده و نام گیرنده، عنوان دوره و تاریخ اتمام را نمایش دهد. اگر UUID وجود نداشت، خطای 404 برگردانید. هیچ ورود (login) اضافی لازم نیست.

این انتخاب طراحیِ واحد، از حریم خصوصی محافظت کرده و از حملات شمارشگری (enumeration attacks) جلوگیری می‌کند.

طراحی قالب Blade

طراحی گواهینامه شما در یک Blade view استاندارد قرار دارد، اما باید با آن متفاوت از یک صفحه وب معمولی برخورد کنید. موتورهای رندر PDF مانند مرورگرها رفتار نمی‌کنند. استایل‌شیت‌های خارجی غیرقابل اعتماد هستند زیرا ممکن است تبدیل‌کننده PDF آن‌ها را در همان بافت (context) دریافت نکند. منحصراً از CSS درون‌خطی (inline) استفاده کنید.

به ابعاد فیزیکی فکر کنید. اگر یک گواهینامه افقی (landscape) می‌خواهید، عرض و ارتفاع کانتینر را مستقیماً تنظیم کنید:

<div style="width: 11in; height: 8.5in; position: relative; padding: 40px; font-family: Georgia, serif;">

امضاها، مهرها و حاشیه‌ها را با استفاده از موقعیت‌دهی مطلق (absolute positioning) در داخل آن کانتینر قرار دهید. فونت‌های Web-safe امن‌ترین گزینه شما هستند، اگرچه برخی از موتورهای PDF اگر دقت کنید اجازه جاسازی فونت (font embedding) را می‌دهند. از تکیه سنگین بر CSS Grid یا Flexbox پیشرفته خودداری کنید، مگر اینکه بدانید موتور رندر شما از آن‌ها پشتیبانی می‌کند. برای سازگاری گسترده، چیدمان‌های قدیمی table هنوز مشکلاتی در تراز کردن (alignment) را حل می‌کنند که CSS مدرن نمی‌تواند در داخل یک موتور PDF تضمین کند.

کدنویسی (markup) را تمیز نگه دارید. هرچه HTML ساده‌تر باشد، احتمال اینکه تبدیل‌کننده با یک چیدمان شکسته شما را غافلگیر کند، کمتر است.

افزودن کد QR

بسته Simple QRCode را نصب کنید. شما به یک کد QR نیاز دارید که مستقیماً به صفحه تأیید شما اشاره کند. در قالب Blade خود، آن را به این صورت رندر کنید:

<img src="{!! QrCode::size(150)->generate(route('certificates.verify', $certificate->uuid)) !!}" style="position: absolute; bottom: 40px; right: 40px;">

وقتی کارفرمایی آن کد را با تلفن اسکن می‌کند، وارد اپلیکیشن Laravel شما شده و رکورد زنده را مشاهده می‌کند. این پل ارتباطی فیزیکی به دیجیتال است که گواهینامه را قابل تأیید می‌کند. یک PDF زیبا به تنهایی چیزی را ثابت نمی‌کند. کد QR اعتماد را فوری و بصری می‌کند.

انتخاب یک موتور PDF

توسعه‌دهندگان Laravel معمولاً یکی از سه مسیر را انتخاب می‌کنند. هر کدام مزایا و معایب واقعی خود را دارند.

DomPDF کاملاً در PHP اجرا می‌شود. آن را از طریق Composer نصب کنید، HTML رندر شده توسط Blade را به آن پاس دهید و خروجی را ذخیره کنید. این ابزار به نرم‌افزار سرور اضافی نیاز ندارد که اگر از هاست اشتراکی استفاده می‌کنید، آن را جذاب می‌کند. نقطه ضعف آن پشتیبانی از CSS است. ابزارهای چیدمان مدرن مانند Flexbox و Grid یا به طور جزئی پشتیبانی می‌شوند یا خراب هستند. فونت‌های وب سفارشی می‌توانند دردسرساز باشند و پس‌زمینه‌های پیچیده اغلب به درستی رندر نمی‌شوند. اگر طراحی گواهینامه شما محافظه‌کارانه است، DomPDF کار می‌کند. اگر به دقت بالا نیاز دارید، شما را ناامید خواهد کرد.

Browsershot رویکرد متفاوتی دارد. این ابزار از Puppeteer برای هدایت یک نمونه headless Chrome استفاده می‌کند تا HTML شما را دقیقاً همان‌طور که مرورگر می‌بیند رندر کرده و یک PDF صادر کند. از آنجایی که این یک Chrome واقعی است، استایل‌های Tailwind، فونت‌های سفارشی و چیدمان‌های واکنش‌گرا (responsive) شما همگی به طور کامل منتقل می‌شوند. چالش اصلی، تنظیمات سرور است. شما باید Node.js، Puppeteer و Chrome را نصب داشته باشید. در برخی پلتفرم‌ها، باید sandbox Chrome را غیرفعال کنید یا حافظه را با دقت مدیریت کنید تا از متوقف شدن فرآیندها جلوگیری شود. اگر کنترل سرور خود را در دست دارید و به بازسازی طراحی با دقت پیکسل (pixel-perfect) نیاز دارید، Browsershot بی‌رقیب است.

HTML to PDF API مسیر سوم است. شما HTML رندر شده‌ی Blade خود را به صورت یک رشته (string) جمع‌آوری می‌کنید، آن را به یک سرویس خارجی POST می‌کنید و یک PDF دریافت می‌کنید. مزیت عملی این روش، عدم وابستگی به سمت سرور است. شما نیازی به نصب Chrome ندارید و درگیر عیب‌یابی مشکلات عجیب و غریب فونت در PHP نمی‌شوید. در نهایت، یک PDF برداری با فرمت صحیح دریافت می‌کنید. اکثر این سرویس‌ها به ازای هر سند هزینه دریافت می‌کنند، اما برای بسیاری از کاربردها، سادگی عملیاتی ارزش این هزینه را دارد. اگر می‌خواهید بدون درگیر شدن با کارهای مدیریت سیستم (sysadmin)، خروجی باکیفیتی داشته باشید، این معمولاً سریع‌ترین راه برای رسیدن به مرحله تولید (production) است.

گردش کار کامل

زمانی که موتور خود را انتخاب کردید، خط لوله (pipeline) بسیار ساده است. یک رویداد تکمیل دوره باید یک شنونده (listener) را فعال کند که مراحل زیر را انجام دهد:

  1. یک UUID تولید کرده و رکورد Certificate را ایجاد کنید.
  2. نمای Blade را با استفاده از view('certificates.pdf', compact('certificate'))->render() به یک رشته HTML تبدیل کنید.
  3. آن رشته HTML را به سرویس یا پکیج PDF انتخابی خود ارسال کنید.
  4. فایل حاصل را در یک مسیر منطقی مانند certificates/2024/06/uuid.pdf در storage/app/certificates/ ذخیره کنید.
  5. فایل را به یک اعلان (notification) پیوست کرده و به‌طور خودکار برای یادگیرنده ایمیل کنید.

سیستم اعلان Laravel فرآیند ارسال ایمیل را به خوبی مدیریت می‌کند. اگر رندر کردن بیش از یک ثانیه طول می‌کشد، عملیات تولید را در صف (Queue) قرار دهید. شما نمی‌خواهید یادگیرنده در حین بارگذاری صفحه، منتظر یک فایل PDF بماند.

وقتی کاربر فایل را دانلود می‌کند، یک PDF استاندارد دریافت می‌کند که در هر نمایشگری باز می‌شود. وقتی آن را چاپ می‌کند، متن برداری شفاف باقی می‌ماند. وقتی کارفرما کد QR را اسکن می‌کند، اپلیکیشن Laravel شما UUID را در پایگاه داده تأیید کرده و اطلاعات را نمایش می‌دهد.

چرا خروجی برداری غیرقابل مذاکره است

برخی از روش‌های تولید PDF خروجی رستری (rasterized) تولید می‌کنند. این بدان معناست که هر بخش از متن به یک تصویر تبدیل می‌شود. اگر زوم کنید، حروف تار می‌شوند و حجم فایل‌ها به شدت بالا می‌رود. همچنین صفحه‌خوان‌ها و موتورهای جستجو نمی‌توانند متن را استخراج کنند.

همیشه تأیید کنید که روش انتخابی شما یک PDF برداری واقعی تولید می‌کند. متن باید قابلیت انتخاب (selectable) داشته باشد. خطوط باید در زوم ۴۰۰ درصد نیز شفاف بمانند. یادگیرندگان شما شایسته سندی هستند که رسمی به نظر برسد، نه اسکرین‌شاتی که به عنوان PDF ذخیره شده است.

خروجی برداری همچنین هزینه‌های ذخیره‌سازی شما را پایین نگه می‌دارد. یک صفحه متن برداری ممکن است حدود پنجاه کیلوبایت وزن داشته باشد، در حالی که همان صفحه به صورت یک تصویر با وضوح بالا می‌تواند از دو مگابایت فراتر رود. در مقیاس بالا، این تفاوت بسیار مهم است.

نکته‌ای که می‌توانید از آن استفاده کنید

ساخت این سیستم به صورت داخلی، فقط برای صرفه‌جویی در هزینه‌های پلتفرم‌های گواهینامه SaaS نیست؛ بلکه بحث اعتبار در میان است. وقتی یک یادگیرنده می‌تواند سندی را چاپ کند، آن را روی میز به کسی بدهد و اجازه دهد یک فرد غریبه با گوشی آن را فوراً تأیید کند، شما چیزی ماندگار به او داده‌اید. شما طراحی را کنترل می‌کنید. شما داده‌ها را کنترل می‌کنید. این اعتماد متعلق به برند شماست، نه زیردامنه یک فروشنده.

شما در اپلیکیشن‌های Laravel خود چگونه تولید و تأیید گواهینامه را مدیریت می‌کنید؟ دوست دارم در نظرات بشنوم که چه روشی برای شما جواب داده است.