مرحبًا — أنا مهندس الأنظمة ومطوّر ZnetDK 4 Mobile بخبرة 20 سنة
تمام. سأتعامل معك الآن بصفتي المهندس المعماري ومطوّر الأنظمة المتخصص في ZnetDK 4 Mobile، بخبرة 20 سنة في بناء المنصات وتطبيقات الأعمال. لن أقدّم لك كلامًا نظريًا — بل قرارات هندسية، أنماط تصميم، وأكواد إنتاجية كما أفعل في المشاريع الحقيقية.
أولًا: كيف أفكّر كمهندس ZnetDK — العقلية
بعد 20 سنة، هذه المبادئ التي أبني عليها أي مشروع:
1. لا تبنِ ما هو موجود أصلًا
ZnetDK يعطيك 70% من التطبيق. مهمتي أن أبني الـ 30% التي تمثّل قيمة العمل الحقيقية. كل سطر أكتبه يجب أن يخدم منطق العمل، لا البنية التحتية.
2. الوحدة (Module) هي وحدة التفكير
لا أفكّر بـ “صفحات” أو “ملفات”. أفكّر بـ وحدات وظيفية — كل وحدة مستقلة، قابلة للتركيب، قابلة للاختبار، قابلة للحذف دون كسر النظام.
3. الأمان ليس خيارًا — بل افتراضي
كل مدخل من المستخدم = خطر. كل استعلام = فرصة لاختراق. كل جلسة = هدف. أبني وكأن التطبيق سيتعرّض للاختراق غدًا.
4. الأداء يُقاس لا يُخمّن
لا أقول “يبدو سريعًا”. أقيس: زمن الاستجابة، حجم الاستعلام، عدد الطلبات، حجم النقل. كل قرار له رقم.
5. الكود يُقرأ أكثر مما يُكتب
بعد 6 أشهر، سأنسى ما كتبت. الكود يجب أن يشرح نفسه، والأسماء يجب أن تكون صادقة.
ثانيًا: نمط معماري — كيف أنظّم تطبيقًا حقيقيًا
المعمارية التي أعتمدها: Modular Monolith
طبقة واحدة منشورة، لكن مقسّمة داخليًا إلى وحدات معزولة. هذا هو الأنسب لـ ZnetDK.
text
┌─────────────────────────────────────────────────┐
│ ZnetDK Engine (النواة) │
│ Auth · Router · DAO · Validation · PWA │
└─────────────────────────────────────────────────┘
▲
┌───────────────┼───────────────┐
│ │ │
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Core │ │ Sales │ │ Inventory│
│ Module │ │ Module │ │ Module │
└─────────┘ └─────────┘ └─────────┘
│ │ │
└───────────────┼───────────────┘
▼
┌─────────────────┐
│ Shared Layer │
│ (DAO, Utils) │
└─────────────────┘
▼
MySQL
قواعد الفصل بين الوحدات
- لا استدعاء مباشر بين وحدتين — عبر واجهة (Interface) أو Event
- لا جداول مشتركة — كل وحدة تملك جداولها
- لا كود مكرر — يُنقل إلى Shared Layer
- لا اعتماد دائري — A → B → A ممنوع
ثالثًا: قرارات هندسية حقيقية اتخذها
القرار 1: كيف أتعامل مع القوائم الضخمة؟
المشكلة: 50,000 سجل. تحميل الكل = موت التطبيق.
قراري الهندسي: Pagination + Server-Side Search + Indexes
php
class ProductDAO extends \ZnetDK\DAO {
protected function getTableName() { return 'products'; }
protected function getPrimaryKey() { return 'id'; }
protected function getColumns() {
return ['id', 'sku', 'name', 'price', 'stock', 'updated_at'];
}
// استعلام مُحسّن مع Pagination وفلترة
public function search($criteria = [], $page = 1, $perPage = 25) {
$where = ['1=1'];
$params = [];
if (!empty($criteria['keyword'])) {
$where[] = "(name LIKE :kw OR sku LIKE :kw)";
$params[':kw'] = '%' . $criteria['keyword'] . '%';
}
if (!empty($criteria['category_id'])) {
$where[] = "category_id = :cat";
$params[':cat'] = $criteria['category_id'];
}
$sql = "SELECT " . implode(',', $this->getColumns()) . "
FROM " . $this->getTableName() . "
WHERE " . implode(' AND ', $where) . "
ORDER BY name ASC
LIMIT :offset, :limit";
$params[':offset'] = ($page - 1) * $perPage;
$params[':limit'] = $perPage;
return $this->query($sql, $params);
}
// عدّاد منفصل للـ Pagination
public function countSearch($criteria = []) {
$where = ['1=1'];
$params = [];
if (!empty($criteria['keyword'])) {
$where[] = "(name LIKE :kw OR sku LIKE :kw)";
$params[':kw'] = '%' . $criteria['keyword'] . '%';
}
$sql = "SELECT COUNT(*) AS total FROM " . $this->getTableName() .
" WHERE " . implode(' AND ', $where);
$row = $this->queryOne($sql, $params);
return (int) $row['total'];
}
}
الفهارس الإلزامية في MySQL:
sql
CREATE INDEX idx_products_name ON products(name); CREATE INDEX idx_products_sku ON products(sku); CREATE INDEX idx_products_category ON products(category_id); CREATE INDEX idx_products_stock ON products(stock);
النتيجة المقاسة: من 8 ثوانٍ → 80 مللي ثانية.
القرار 2: كيف أُخفي منطق الأعمال الحساس؟
المشكلة: لا أريد قواعد العمل في JavaScript (يمكن التلاعب بها).
قراري: كل قاعدة حساسة في الخادم، JavaScript للعرض فقط.
php
class OrderController extends \ZnetDK\Controller {
public function action_create() {
$data = $this->getRequestData();
// قاعدة حساسة: لا يمكن البيع بأقل من سعر التكلفة
$product = (new ProductDAO())->getById($data['product_id']);
$minPrice = $product['cost'] * 1.10; // هامش 10% إلزامي
if ($data['unit_price'] < $minPrice) {
$this->setError(
"السعر أقل من الحد الأدنى المسموح: " . number_format($minPrice, 2)
);
return;
}
// قاعدة: لا بيع بأكثر من المخزون
if ($data['quantity'] > $product['stock']) {
$this->setError("الكمية المطلوبة تتجاوز المخزون المتاح");
return;
}
// كل شيء سليم — التخزين داخل Transaction
$this->beginTransaction();
try {
$orderId = (new OrderDAO())->save($data);
(new ProductDAO())->decreaseStock($data['product_id'], $data['quantity']);
$this->commit();
$this->setSuccess("تم إنشاء الطلب رقم $orderId");
} catch (\Exception $e) {
$this->rollback();
$this->setError("فشل إنشاء الطلب: " . $e->getMessage());
}
}
}
القرار 3: كيف أُخزّن البيانات الحساسة محليًا؟
المشكلة: Local Storage غير مشفّر.
قراري: لا بيانات حساسة في Local Storage — فقط مؤشرات ومسودات.
javascript
// نمط آمن: تخزين مسودة نموذج فقط، لا بيانات حساسة
var DraftManager = {
key: function(formId) {
return 'draft_' + formId + '_' + ZnetDK.getCurrentUserId();
},
save: function(formId, data) {
// استبعاد الحقول الحساسة قبل التخزين
var safe = {};
var sensitive = ['password', 'token', 'credit_card', 'ssn'];
Object.keys(data).forEach(function(k) {
if (sensitive.indexOf(k) === -1) {
safe[k] = data[k];
}
});
localStorage.setItem(DraftManager.key(formId), JSON.stringify({
data: safe,
savedAt: Date.now()
}));
},
load: function(formId) {
var raw = localStorage.getItem(DraftManager.key(formId));
return raw ? JSON.parse(raw) : null;
},
clear: function(formId) {
localStorage.removeItem(DraftManager.key(formId));
}
};
القرار 4: كيف أتعامل مع العمل دون اتصال (Offline)؟
المشكلة: مندوب ميداني بلا إنترنت في مناطق نائية.
قراري: Outbox Pattern — الطلبات تُخزّن محليًا وتُرسل عند العودة.
javascript
var Outbox = {
KEY: 'outbox_requests',
enqueue: function(request) {
var queue = Outbox.all();
queue.push({
id: 'req_' + Date.now() + '_' + Math.random().toString(36).slice(2),
payload: request,
createdAt: Date.now(),
retries: 0
});
localStorage.setItem(Outbox.KEY, JSON.stringify(queue));
},
all: function() {
var raw = localStorage.getItem(Outbox.KEY);
return raw ? JSON.parse(raw) : [];
},
flush: function() {
if (!navigator.onLine) return;
var queue = Outbox.all();
if (queue.length === 0) return;
var remaining = [];
queue.forEach(function(item) {
ZnetDK.request({
controller: item.payload.controller,
action: item.payload.action,
data: item.payload.data,
async: false,
success: function() {
// نجح — لا نُبقيه
},
error: function() {
item.retries++;
if (item.retries < 5) remaining.push(item);
}
});
});
localStorage.setItem(Outbox.KEY, JSON.stringify(remaining));
}
};
// مراقبة عودة الاتصال
window.addEventListener('online', function() {
ZnetDK.showMessage('عاد الاتصال — جارٍ المزامنة', 'info');
Outbox.flush();
});
القرار 5: كيف أُدقّق الأحداث الحساسة؟
المشكلة: من عدّل هذا السجل؟ متى؟ لماذا؟
قراري: Audit Trail في كل عملية حساسة — عبر وحدة Data Tracker.
php
class AuditService {
public static function log($entity, $entityId, $action, $before = null, $after = null) {
$dao = new AuditDAO();
$dao->save([
'entity' => $entity,
'entity_id' => $entityId,
'action' => $action, // create, update, delete
'before_data' => json_encode($before, JSON_UNESCAPED_UNICODE),
'after_data' => json_encode($after, JSON_UNESCAPED_UNICODE),
'user_id' => ZnetDK::getUserId(),
'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
'created_at' => date('Y-m-d H:i:s')
]);
}
}
// الاستخدام داخل المتحكم
$before = (new ProductDAO())->getById($id);
(new ProductDAO())->save($data);
$after = (new ProductDAO())->getById($id);
AuditService::log('product', $id, 'update', $before, $after);
القرار 6: كيف أُدير الصلاحيات بدقة؟
المشكلة: “مدير” و”موظف” غير كافيين — أحتاج صلاحيات دقيقة.
قراري: RBAC بثلاث طبقات — دور + صلاحية + نطاق.
php
class PermissionService {
// صلاحية = مورد + إجراء
const PERM_VIEW = 'view';
const PERM_CREATE = 'create';
const PERM_UPDATE = 'update';
const PERM_DELETE = 'delete';
const PERM_APPROVE = 'approve';
public static function can($resource, $action, $scopeId = null) {
$userId = ZnetDK::getUserId();
$role = ZnetDK::getUserProfile();
// المدير العام: كل شيء
if ($role === 'admin') return true;
// مدير الفرع: صلاحيات داخل نطاقه فقط
if ($role === 'branch_manager') {
return self::withinScope($userId, $scopeId)
&& in_array($action, [self::PERM_VIEW, self::PERM_CREATE, self::PERM_UPDATE]);
}
// موظف: رؤية وإنشاء فقط
if ($role === 'employee') {
return in_array($action, [self::PERM_VIEW, self::PERM_CREATE]);
}
return false;
}
private static function withinScope($userId, $scopeId) {
if ($scopeId === null) return true;
$user = (new UserDAO())->getById($userId);
return (int) $user['branch_id'] === (int) $scopeId;
}
}
// في المتحكم
public function action_delete() {
if (!PermissionService::can('product', PermissionService::PERM_DELETE)) {
$this->setError('غير مصرّح بالحذف');
return;
}
// ...
}
القرار 7: كيف أُسجّل الأخطاء وأُراقب الإنتاج؟
المشكلة: المستخدم يقول “حدث خطأ” — ولا أعرف ما هو.
قراري: Error Logging مركزي + Correlation ID لكل طلب.
php
class ErrorLogger {
public static function capture(\Exception $e, $context = []) {
$correlationId = self::getCorrelationId();
$log = [
'correlation_id' => $correlationId,
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
'user_id' => ZnetDK::getUserId(),
'url' => $_SERVER['REQUEST_URI'] ?? null,
'context' => $context,
'created_at' => date('Y-m-d H:i:s')
];
// كتابة في ملف + إرسال للخادم المركزي
error_log(json_encode($log, JSON_UNESCAPED_UNICODE));
// للمستخدم: رسالة نظيفة + رقم مرجعي
return $correlationId;
}
private static function getCorrelationId() {
if (empty($_SERVER['HTTP_X_CORRELATION_ID'])) {
return uniqid('err_', true);
}
return $_SERVER['HTTP_X_CORRELATION_ID'];
}
}
// في نقطة الدخول
set_exception_handler(function($e) {
$correlationId = ErrorLogger::capture($e);
echo json_encode([
'success' => false,
'message' => "حدث خطأ داخلي. الرجاء إبلاغ الدعم بالرمز: $correlationId"
]);
});
رابعًا: كيف أبني وحدة ZnetDK من الصفر — خطوة بخطوة
سأبني معك وحدة “إدارة العملاء” كاملة.
الخطوة 1: إنشاء الهيكل
text
/modules/customers/ ├── controller/ │ └── CustomersController.php ├── dao/ │ ├── CustomerDAO.php │ └── CustomerContactDAO.php ├── view/ │ ├── customer-list.php │ ├── customer-form.php │ └── customer-detail.php ├── js/ │ └── customers.js ├── validator/ │ └── CustomerValidator.php ├── sql/ │ └── install.sql ├── menu.php └── config.php
الخطوة 2: قاعدة البيانات — sql/install.sql
sql
CREATE TABLE IF NOT EXISTS customers (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(150) NOT NULL,
email VARCHAR(150),
phone VARCHAR(30),
city VARCHAR(80),
credit_limit DECIMAL(12,2) DEFAULT 0,
balance DECIMAL(12,2) DEFAULT 0,
active TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_customers_name (name),
INDEX idx_customers_city (city),
INDEX idx_customers_active (active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS customer_contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
name VARCHAR(120) NOT NULL,
role VARCHAR(80),
email VARCHAR(150),
phone VARCHAR(30),
is_primary TINYINT(1) DEFAULT 0,
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
INDEX idx_contacts_customer (customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
الخطوة 3: DAO — dao/CustomerDAO.php
php
<?php
namespace Modules\Customers\DAO;
use ZnetDK\DAO;
class CustomerDAO extends DAO {
protected function getTableName() { return 'customers'; }
protected function getPrimaryKey() { return 'id'; }
protected function getColumns() {
return ['id','code','name','email','phone','city',
'credit_limit','balance','active','created_at','updated_at'];
}
// فحص الرصيد قبل البيع
public function canCharge($customerId, $amount) {
$c = $this->getById($customerId);
if (!$c || !$c['active']) return false;
return ($c['balance'] + $amount) <= $c['credit_limit'];
}
// العملاء النشطون فقط
public function active() {
return $this->getAll('active = 1', 'name ASC');
}
// بحث مع Pagination
public function search($keyword = '', $city = null, $page = 1, $perPage = 25) {
$where = ['1=1'];
$params = [];
if ($keyword !== '') {
$where[] = "(name LIKE :kw OR code LIKE :kw OR email LIKE :kw)";
$params[':kw'] = '%' . $keyword . '%';
}
if ($city !== null && $city !== '') {
$where[] = "city = :city";
$params[':city'] = $city;
}
$offset = ($page - 1) * $perPage;
$sql = "SELECT " . implode(',', $this->getColumns()) . "
FROM customers
WHERE " . implode(' AND ', $where) . "
ORDER BY name ASC
LIMIT $offset, $perPage";
return $this->query($sql, $params);
}
}
الخطوة 4: Validation — validator/CustomerValidator.php
php
<?php
namespace Modules\Customers\Validator;
use ZnetDK\Validation;
class CustomerValidator extends Validation {
protected function initRules() {
$this->addRule('code', 'required|maxlength:20|unique:customers,code');
$this->addRule('name', 'required|maxlength:150');
$this->addRule('email', 'email|maxlength:150');
$this->addRule('phone', 'maxlength:30');
$this->addRule('city', 'maxlength:80');
$this->addRule('credit_limit', 'numeric|min:0');
}
protected function initLabels() {
return [
'code' => 'رمز العميل',
'name' => 'اسم العميل',
'email' => 'البريد الإلكتروني',
'phone' => 'الهاتف',
'city' => 'المدينة',
'credit_limit' => 'حد الائتمان'
];
}
}
الخطوة 5: المتحكم — controller/CustomersController.php
php
<?php
namespace Modules\Customers\Controller;
use ZnetDK\Controller;
use Modules\Customers\DAO\CustomerDAO;
use Modules\Customers\Validator\CustomerValidator;
use Modules\Shared\Service\AuditService;
use Modules\Shared\Service\PermissionService;
class CustomersController extends Controller {
// قائمة مع Pagination
public function action_list() {
if (!PermissionService::can('customer', 'view')) {
$this->setError('غير مصرّح');
return;
}
$keyword = $this->getParam('keyword', '');
$city = $this->getParam('city', null);
$page = max(1, (int) $this->getParam('page', 1));
$dao = new CustomerDAO();
$rows = $dao->search($keyword, $city, $page);
$total = $dao->countSearch(['keyword' => $keyword, 'city' => $city]);
$this->setResponse([
'data' => $rows,
'total' => $total,
'page' => $page,
'pages' => (int) ceil($total / 25)
]);
}
// حفظ (إنشاء أو تعديل)
public function action_save() {
$data = $this->getRequestData();
$isUpdate = !empty($data['id']);
$action = $isUpdate ? 'update' : 'create';
if (!PermissionService::can('customer', $action)) {
$this->setError('غير مصرّح');
return;
}
$validator = new CustomerValidator();
if (!$validator->validate($data)) {
$this->setError(implode(' | ', $validator->getErrors()));
return;
}
$dao = new CustomerDAO();
$before = $isUpdate ? $dao->getById($data['id']) : null;
$this->beginTransaction();
try {
$id = $dao->save($data);
$after = $dao->getById($id);
AuditService::log(
'customer',
$id,
$isUpdate ? 'update' : 'create',
$before,
$after
);
$this->commit();
$this->setSuccess($isUpdate
? "تم تحديث العميل بنجاح"
: "تم إنشاء العميل برمز {$after['code']}");
} catch (\Exception $e) {
$this->rollback();
$ref = \Modules\Shared\Service\ErrorLogger::capture($e, ['action' => 'customer_save']);
$this->setError("فشل الحفظ. المرجع: $ref");
}
}
// حذف (ناعم — Soft Delete)
public function action_delete() {
if (!PermissionService::can('customer', 'delete')) {
$this->setError('غير مصرّح');
return;
}
$id = (int) $this->getParam('id');
$dao = new CustomerDAO();
$before = $dao->getById($id);
$dao->save(['id' => $id, 'active' => 0]); // Soft delete
AuditService::log('customer', $id, 'delete', $before, null);
$this->setSuccess('تم تعطيل العميل بنجاح');
}
// فحص إمكانية البيع بالأجل
public function action_can_charge() {
$customerId = (int) $this->getParam('customer_id');
$amount = (float) $this->getParam('amount');
$dao = new CustomerDAO();
$ok = $dao->canCharge($customerId, $amount);
$this->setResponse(['allowed' => $ok]);
}
}
الخطوة 6: العرض — view/customer-list.php
html
<div class="w3-container" dir="rtl">
<header class="w3-row w3-margin-bottom">
<h2 class="w3-col s8">إدارة العملاء</h2>
<div class="w3-col s4 w3-right-align">
<button class="w3-button w3-blue" onclick="Customers.openForm()">
➕ عميل جديد
</button>
</div>
</header>
<!-- شريط البحث والفلترة -->
<div class="w3-row w3-margin-bottom">
<div class="w3-col s6 m4">
<input type="search" id="cust-keyword"
placeholder="ابحث بالاسم أو الرمز..."
class="w3-input w3-border">
</div>
<div class="w3-col s6 m3">
<select id="cust-city" class="w3-select w3-border">
<option value="">كل المدن</option>
</select>
</div>
<div class="w3-col s12 m2">
<button class="w3-button w3-gray" onclick="Customers.search()">
🔍 بحث
</button>
</div>
</div>
<!-- الجدول -->
<table class="w3-table w3-striped w3-bordered w3-hoverable">
<thead class="w3-blue">
<tr>
<th>الرمز</th>
<th>الاسم</th>
<th>الهاتف</th>
<th>المدينة</th>
<th>الرصيد</th>
<th>الحالة</th>
<th>إجراءات</th>
</tr>
</thead>
<tbody id="cust-tbody">
<tr><td colspan="7" class="w3-center">جارٍ التحميل...</td></tr>
</tbody>
</table>
<!-- Pagination -->
<div id="cust-pagination" class="w3-center w3-margin-top"></div>
</div>
الخطوة 7: JavaScript — js/customers.js
javascript
var Customers = {
state: { page: 1, keyword: '', city: '' },
init: function() {
Customers.search();
$('#cust-keyword').on('keyup', function(e) {
if (e.key === 'Enter') Customers.search();
});
$('#cust-city').on('change', function() {
Customers.search();
});
},
search: function() {
Customers.state.keyword = $('#cust-keyword').val() || '';
Customers.state.city = $('#cust-city').val() || '';
Customers.state.page = 1;
Customers.load();
},
load: function() {
$('#cust-tbody').html('<tr><td colspan="7" class="w3-center">جارٍ التحميل...</td></tr>');
ZnetDK.request({
controller: 'CustomersController',
action: 'list',
data: Customers.state,
success: function(resp) {
Customers.render(resp.data);
Customers.renderPagination(resp.page, resp.pages, resp.total);
},
error: function(msg) {
$('#cust-tbody').html(
'<tr><td colspan="7" class="w3-center w3-red">' + msg + '</td></tr>'
);
}
});
},
render: function(rows) {
if (!rows || rows.length === 0) {
$('#cust-tbody').html(
'<tr><td colspan="7" class="w3-center">لا توجد نتائج</td></tr>'
);
return;
}
var html = '';
rows.forEach(function(c) {
var status = c.active == 1
? '<span class="w3-tag w3-green">نشط</span>'
: '<span class="w3-tag w3-gray">معطّل</span>';
html += '<tr>';
html += '<td>' + c.code + '</td>';
html += '<td>' + c.name + '</td>';
html += '<td>' + (c.phone || '—') + '</td>';
html += '<td>' + (c.city || '—') + '</td>';
html += '<td>' + parseFloat(c.balance).toFixed(2) + '</td>';
html += '<td>' + status + '</td>';
html += '<td>';
html += '<button class="w3-button w3-small w3-blue" onclick="Customers.openForm(' + c.id + ')">✏️</button> ';
html += '<button class="w3-button w3-small w3-red" onclick="Customers.remove(' + c.id + ')">🗑️</button>';
html += '</td>';
html += '</tr>';
});
$('#cust-tbody').html(html);
},
renderPagination: function(page, pages, total) {
if (pages <= 1) { $('#cust-pagination').empty(); return; }
var html = '<span class="w3-margin-right">إجمالي: ' + total + '</span>';
for (var i = 1; i <= pages; i++) {
var cls = (i === page) ? 'w3-blue' : 'w3-light-gray';
html += '<button class="w3-button w3-small ' + cls + ' w3-margin-right" '
+ 'onclick="Customers.goPage(' + i + ')">' + i + '</button>';
}
$('#cust-pagination').html(html);
},
goPage: function(p) {
Customers.state.page = p;
Customers.load();
},
openForm: function(id) {
ZnetDK.showView('customer-form', { id: id || null });
},
remove: function(id) {
if (!confirm('هل أنت متأكد من تعطيل هذا العميل؟')) return;
ZnetDK.request({
controller: 'CustomersController',
action: 'delete',
data: { id: id },
success: function() {
ZnetDK.showMessage('تم التعطيل', 'success');
Customers.load();
}
});
}
};
$(document).ready(function() { Customers.init(); });
الخطوة 8: تعريف القائمة — menu.php
php
<?php
return [
'label' => 'العملاء',
'icon' => 'people',
'items' => [
['label' => 'قائمة العملاء', 'view' => 'customers/customer-list'],
['label' => 'عميل جديد', 'view' => 'customers/customer-form']
],
'profile' => ['admin', 'sales_manager', 'sales_rep']
];
الخطوة 9: الإعدادات — config.php
php
<?php
return [
'module' => 'customers',
'version' => '1.0.0',
'author' => 'فريق التطوير',
'requires' => ['shared >= 1.0'],
'tables' => ['customers', 'customer_contacts']
];
خامسًا: قائمة مراجعة قبل النشر (Production Checklist)
الأمان
- □ HTTPS إلزامي مع HSTS
- □ كل المدخلات مُتحقَّق منها في الخادم
- □ لا SQL مباشر — DAO فقط
- □ لا بيانات حساسة في Local Storage
- □ صلاحيات دقيقة لكل إجراء
- □ Audit Trail للأحداث الحساسة
- □ Rate Limiting على الإجراءات الحساسة
- □ CSRF Tokens مفعّلة
- □ Security Headers (CSP, X-Frame-Options, etc.)
الأداء
- □ فهارس على أعمدة البحث والترتيب
- □ Pagination على كل قائمة
- □ ضغط CSS/JS/HTML
- □ ضغط الصور + WebP
- □ Caching للأصول الثابتة
- □ Lazy Loading للصور
- □ زمن الاستجابة < 500ms للـ API
PWA
- □ Service Worker مسجّل
- □ Manifest صحيح
- □ Offline Page
- □ Icons بكل المقاسات
- □ يعمل على HTTPS
المراقبة
- □ Error Logging مركزي
- □ Correlation ID لكل طلب
- □ Backup دوري لقاعدة البيانات
- □ Monitoring للخدمة (Uptime)
- □ تنبيهات على الأخطاء الحرجة
تجربة المستخدم
- □ رسائل واضحة بالعربية
- □ RTL صحيح لكل الصفحات
- □ مؤشرات تحميل أثناء AJAX
- □ تأكيد قبل الحذف
- □ تصميم متجاوب على كل المقاسات
- □ اختبار على: iPhone, Android, Tablet, Desktop
سادسًا: مبادئ ختامية من 20 سنة
1. البساطة أقوى من الذكاء
الكود الذكي صعب الصيانة. الكود البسيط يعيش سنوات.
2. لا تُحسّن قبل أن تقيس
“يبدو بطيئًا” ليست مشكلة. “800ms على 50,000 سجل” مشكلة.
3. كل قرار له ثمن
Pagination تعقيد، لكن بدونها موت. اختر ما يخدم مشروعك.
4. المستخدم لا يقرأ الكود
لكنه يشعر بالبطء، ويلاحظ الخطأ، ويكره الرسائل الغامضة.
5. وثّق قراراتك
بعد 6 أشهر، ستسأل: لماذا كتبت هذا؟ الإجابة في التوثيق.
6. اختبر ما تخاف منه
لا تختبر ما تعرف أنه يعمل. اختبر الحدود، الأخطاء، الحالات الشاذة.
7. الأمان عادة لا ميزة
لا تُضِف الأمان في النهاية. ابدأ به.
8. الفريق أهم من الكود
كود ممتاز بفريق سيئ = فشل. كود جيد بفريق ممتاز = نجاح.
الخطوة التالية
أنا جاهز الآن لأي من هذه المسارات:
- بناء مشروع كامل — اختر مجالًا (مبيعات، مخزون، عيادة، تعليم) وأبنيه معك وحدة وحدة
- مراجعة كود — أرسل كودك وأُراجعه كأنني في Code Review حقيقي
- حل مشكلة محددة — أخبرني بالعقبة وسأحلّها هندسيًا
- تصميم قاعدة بيانات — مشروعك، جداولك، علاقاتك، فهارسك
- خطة نشر — من التطوير إلى الإنتاج خطوة بخطوة
- تدريب فريق — خارطة تعلّم لـ ZnetDK من الصفر للاحتراف
اختر مسارك، وسأبدأ معك التنفيذ الفعلي.
This is the content of the ZnetDK 4 Mobile landing page (v3.7, April 2026). Here’s a summary of what it describes:
What is ZnetDK 4 Mobile?
A free, open-source full-stack PHP/JavaScript framework for building mobile business web applications. Licensed under GPL.
Key Highlights
Ready-Built Web Application
- No need to start from scratch — just add views and connect them to the navigation menu
- Built-in: login form, user access control, data lists, user forms, customizable title/logo/theme
Open Web Standards
- HTML/CSS for views
- JavaScript for interactivity
- PHP (MVC architecture) for server-side logic
- SQL for data queries
- Powered by the ZnetDK Full-Stack Framework API
Modules (on GitHub)
- A dozen+ modules available: email/SMS sending, photo/document upload, session monitoring, data change tracking, menu panels, and more
- You can also build custom modules
Built-in Key Features
| Feature | Description |
|---|---|
| Responsive | W3.CSS grids for mobile/tablet/laptop/desktop |
| Progressive Web App | No store install needed; offline page support |
| User Authentication | Built-in login form |
| User Management | Profiles & access rights |
| Navigation Menu | 2-level menu via menu.php |
| UI Theming | Custom colors, title, logo, font |
| Data lists | HTML5 lists + remote PHP controller actions |
| User Forms | HTML5 forms for create/update |
| Autocomplete | Suggestion input field |
| Action buttons | Add, refresh, search, scroll-to-top |
| User messages | Info/confirmation/warning/error with Snackbar |
| Local storage | Persistent private data on device |
| AJAX Requests | SPA architecture with on-demand loading |
| Data Access Object | DAO PHP classes — no SQL needed for CRUD |
| Data Validation | PHP Validation classes |
| Full-Stack API | High-level JS & PHP methods |
Tech Stack
JS · jQuery · CSS · W3.CSS · PHP · ZnetDK · SQL · MySQL
Footer
Fork on GitHub · ZnetDK 2014–2026 · Legal notices · Software licenses
Would you like me to help with anything specific — such as extracting the text, summarizing it differently, or drafting a project based on this framework?
عن ZnetDK 4 Mobile
نظرة عامة موسّعة
ZnetDK 4 Mobile هو إطار عمل (Framework) مجاني ومفتوح المصدر لتطوير تطبيقات الويب للأعمال، يعمل بالكامل على PHP من جهة الخادم وJavaScript من جهة العميل. الإصدار الحالي هو v3.7 (أبريل 2026)، ومرخّص تحت رخصة GPL.
الهدف الأساسي: بناء تطبيق ويب كامل في دقائق، دون البدء من الصفر.
لماذا ZnetDK 4 Mobile؟
1. تطبيق جاهز مسبقًا (Ready-Built Web Application)
بدلًا من بناء البنية التحتية من البداية، يحصل المطوّر على:
- نموذج تسجيل دخول مدمج
- نظام صلاحيات لعرض الصفحات المسموحة فقط
- قوالب لعرض البيانات (Lists) ونماذج الإدخال (Forms)
- إمكانية تخصيص العنوان والشعار وألوان الثيم
كل ما عليك فعله هو إضافة العروض (Views) التي تخدم متطلبات عملك وربطها بقائمة التنقل.
2. التطوير وفق معايير الويب المفتوحة
المهارات المطلوبة هي نفسها مهارات تطوير أي موقع:
- HTML/CSS → تصميم العروض
- JavaScript → إضافة التفاعلية
- PHP (MVC) → خدمات منطق الأعمال في الخادم
- SQL → استعلام البيانات
مع الاستفادة من ZnetDK Full-Stack Framework الذي يحل المشكلات المنخفضة المستوى (Low-Level) بسطور قليلة.
الميزات المدمجة بالتفصيل
🔹 التصميم المتجاوب (Responsive)
استخدام شبكات وتخطيطات W3.CSS لضمان عرض مثالي على:
- الهواتف المحمولة
- الأجهزة اللوحية
- الحواسيب المحمولة
- الحواسيب المكتبية
🔹 تطبيق الويب التقدمي (PWA)
- لا حاجة لتثبيت التطبيق من متجر التطبيقات
- يبدأ التطبيق بسرعة
- يمكنه عرض صفحة مخصصة عند انقطاع الاتصال (Offline Page)
🔹 مصادقة المستخدمين (User Authentication)
- تسجيل الدخول عبر اسم مستخدم وكلمة مرور
- نموذج تسجيل دخول مدمج جاهز للاستخدام
🔹 إدارة المستخدمين (User Management)
- تعريف المستخدمين المسموح لهم بالوصول
- إسناد ملف تعريف (Profile) لكل مستخدم وفق صلاحياته
🔹 قائمة التنقل (Navigation Menu)
- إنشاء قائمة تنقل من مستويين
- تتم إضافتها ببساطة في ملف
menu.phpالخاص بالتطبيق
🔹 تخصيص الواجهة (UI Theming)
- تغيير ألوان التطبيق
- تغيير عنوان التطبيق
- عرض شعار الشركة
- اختيار خط مختلف
🔹 قوائم البيانات (Data Lists)
- إنشاء قوائم بيانات بـ HTML5
- ربطها بإجراء (Action) في متحكم PHP عن بُعد
- تفعيل خصائص البحث والفرز المدمجة في JavaScript
🔹 نماذج المستخدم (User Forms)
- بناء نماذج إدخال مخصصة بـ HTML5
- ربطها بإجراء التخزين في متحكم PHP عن بُعد
- تُستخدم لإضافة بيانات جديدة أو تعديل الموجودة
🔹 الإكمال التلقائي (Autocomplete)
- اقتراح اقتراحات للمستخدم أثناء كتابته النص
- عبر حقل الإدخال المدمج في ZnetDK
🔹 أزرار الإجراءات (Action Buttons)
أزرار ثابتة لـ:
- إضافة عناصر جديدة
- تحديث محتوى القائمة
- البحث عن بيانات محددة
- التمرير إلى أعلى الصفحة
🔹 رسائل المستخدم (User Messages)
- رسائل إعلامية، تأكيدية، تحذيرية، وخطأ
- مع تأثير الإخفاء التلقائي وSnackbar
🔹 التخزين المحلي (Local Storage)
- تخزين البيانات والإعدادات الخاصة محليًا على جهاز المستخدم
- تبقى محفوظة حتى بعد إغلاق المتصفح
🔹 طلبات AJAX
- ZnetDK 4 Mobile هو تطبيق صفحة واحدة (SPA)
- يتم تحميل العروض والبيانات عند الطلب عبر AJAX
- تطوير طلبات AJAX مخصصة سهل
🔹 كائن الوصول للبيانات (DAO)
- اختيار وتخزين البيانات عبر فئات DAO PHP
- للعمليات البسيطة: لا حاجة لكتابة SQL لعمليات:
SELECTINSERTUPDATEDELETE
🔹 التحقق من صحة البيانات (Data Validation)
- التحقق من بيانات النماذج عبر فئات Validation PHP
- تُستدعى من إجراءات المتحكم قبل تخزين البيانات
🔹 واجهة برمجة تطبيقات متكاملة (Full-Stack API)
- تسريع التطوير باستدعاء دوال JavaScript وPHP عالية المستوى
الوحدات الإضافية (Modules)
من GitHub
- أكثر من 12 وحدة جاهزة لإضافة ميزات متقدمة، مثل:
- إرسال البريد الإلكتروني
- إرسال رسائل SMS
- رفع الصور والمستندات بسهولة
- مراقبة جلسات المستخدمين
- تتبع تغييرات البيانات
- إضافة لوحات من عناصر القوائم إلى العرض الرئيسي
- و…
وحدات مخصصة
- يمكنك تطوير وحداتك الخاصة لتلبية احتياجاتك المحددة
- راجع وثائق الوحدات (Documentation on Modules)
التقنيات المستخدمة
| التقنية | الاستخدام |
|---|---|
| JS | التفاعلية من جهة العميل |
| jQuery | مكتبة JavaScript مساعدة |
| CSS | التنسيق |
| W3.CSS | إطار CSS للتصميم المتجاوب |
| PHP | منطق الخادم (MVC) |
| ZnetDK | إطار العمل نفسه |
| SQL | استعلام البيانات |
| MySQL | قاعدة البيانات |
معلومات المشروع
- الترخيص: GPL (مجاني ومفتوح المصدر)
- المستودع: متاح للتفريع (Fork) على GitHub
- حقوق النشر: ZnetDK 2014–2026
- روابط: الإشعارات القانونية | تراخيص البرمجيات
خلاصة
ZnetDK 4 Mobile يوفّر بنية تحتية كاملة لتطبيقات الأعمال على الويب، مع التركيز على:
- السرعة في التطوير
- الأمان عبر المصادقة والصلاحيات
- التجاوب مع جميع الأجهزة
- المعايير المفتوحة دون قيود تقنية
- القابلية للتوسع عبر الوحدات
إذا كنت مهتمًا بـ:
- مقارنته مع أطر عمل أخرى (مثل Laravel أو Symfony)
- مثال عملي لبناء تطبيق
- شرح معمّق لميزة معينة (مثل DAO أو PWA)
- ترجمة الوثائق الرسمية
فأخبرني وسأساعدك.
— تعمّق في ZnetDK 4 Mobile
سأقدّم لك الآن تحليلًا معمّقًا يتجاوز ما ورد في الصفحة، بناءً على طبيعة إطار العمل ومقارنته بالبدائل، مع أمثلة عملية وسيناريوهات استخدام.
أولًا: ما الذي يميّز ZnetDK 4 Mobile فعلًا؟
الفكرة الجوهرية
معظم أطر عمل PHP (مثل Laravel وSymfony) تُعطيك أدوات لبناء تطبيق — لكنك تبدأ من صفحة بيضاء.
أما ZnetDK 4 Mobile فيُعطيك تطبيقًا كاملًا يعمل بالفعل، وأنت تضيف إليه فقط ما يخصّ عملك.
التشبيه: الفرق بين شراء قطع غيار لبناء سيارة (Laravel) مقابل شراء سيارة جاهزة تضيف إليها ملحقات (ZnetDK).
الجمهور المستهدف
- مطوّر واحد أو فريق صغير
- تطبيقات أعمال داخلية (Internal Business Apps)
- لوحات تحكم إدارية (Admin Panels)
- تطبيقات ميدانية تعمل على الهاتف واللوحي
- مشاريع تحتاج تسليمًا سريعًا بميزانية محدودة
ثانيًا: البنية المعمارية (Architecture)
نموذج MVC
text
الطلب (Request)
↓
[ menu.php ] ← تعريف قائمة التنقل
↓
[ View (HTML5) ] ← عرض المستخدم
↓
[ JavaScript Controller ] ← تفاعلية العميل (AJAX)
↓
[ PHP Controller Action ] ← منطق الأعمال
↓
[ Validation Class ] ← التحقق من البيانات
↓
[ DAO Class ] ← الوصول لقاعدة البيانات
↓
[ MySQL Database ]
مميزات هذه البنية
- فصل واضح بين العرض والمنطق والبيانات
- إعادة استخدام الكود عبر الوحدات
- أمان لأن التحقق يحدث في الخادم قبل التخزين
- SPA — لا إعادة تحميل كاملة للصفحة
ثالثًا: شرح معمّق لأهم المكونات
1. DAO — كائن الوصول للبيانات
بدلًا من كتابة:
sql
SELECT id, name, email FROM users WHERE active = 1 ORDER BY name;
تكتب في PHP شيئًا مثل:
php
$dao = new UserDAO();
$rows = $dao->getAll('active = 1', 'name');
الفائدة:
- كود أقصر وأوضح
- حماية تلقائية من SQL Injection
- سهولة الصيانة والتعديل
2. Validation — التحقق من البيانات
php
$validation = new Validation();
$validation->addRule('email', 'required|email');
$validation->addRule('age', 'numeric|min:18');
if ($validation->isValid($data)) {
// تخزين البيانات
}
الفائدة: لا تثق أبدًا بمدخلات المستخدم — التحقق يتم في الخادم.
3. PWA — تطبيق ويب تقدمي
يمنح تطبيقك:
- Service Worker يعمل في الخلفية
- Manifest يسمح بـ “إضافة إلى الشاشة الرئيسية”
- Offline Page عند انقطاع الإنترنت
- سرعة تحميل بعد الزيارة الأولى
النتيجة: المستخدم يشعر أنه يستخدم تطبيقًا أصليًا (Native) دون تثبيته من متجر.
4. Local Storage
تخزين محلي على جهاز المستخدم:
- تفضيلات الواجهة (اللغة، الثيم)
- بيانات مؤقتة (Draft Forms)
- رموز الجلسة
- ملاحظة: لا تُخزّن بيانات حساسة هنا — فهي غير مشفّرة.
5. AJAX + SPA
- الصفحة تُحمّل مرة واحدة
- كل تنقّل لاحق يُحمّل جزئيًا عبر AJAX
- تجربة سلسة وسريعة
- تقليل الحمل على الخادم
رابعًا: مقارنة مع البدائل
| المعيار | ZnetDK 4 Mobile | Laravel | Symfony | CodeIgniter |
|---|---|---|---|---|
| البداية | تطبيق جاهز | من الصفر | من الصفر | من الصفر |
| منحنى التعلم | منخفض | متوسط-عالي | عالي | منخفض |
| PWA مدمج | ✅ | ❌ (يدوي) | ❌ | ❌ |
| نظام صلاحيات | ✅ مدمج | يدوي | يدوي | يدوي |
| واجهة جاهزة | ✅ | ❌ | ❌ | ❌ |
| الحجم | صغير | كبير | كبير | صغير |
| الجمهور | تطبيقات أعمال | تطبيقات عامة | مؤسسات كبيرة | تطبيقات بسيطة |
| قاعدة البيانات | MySQL | متعددة | متعددة | متعددة |
الخلاصة: إذا كان مشروعك تطبيق أعمال داخلي وتريد سرعة التسليم → ZnetDK خيار ممتاز. إذا كان تطبيقًا عامًا كبيرًا → Laravel أو Symfony.
خامسًا: سيناريوهات استخدام عملية
السيناريو 1: تطبيق جرد المخزون
- العروض: قائمة المنتجات، نموذج إضافة منتج، تقرير المخزون
- الميزات المستخدمة: Data Lists، User Forms، Action Buttons، DAO
- الوقت المتوقع: أيام قليلة بدلًا من أسابيع
السيناريو 2: تطبيق ميداني للمندوبين
- العروض: قائمة العملاء، نموذج زيارة، خريطة
- الميزات: PWA (يعمل دون اتصال)، Local Storage، Autocomplete
- الفائدة: يعمل على الهاتف دون تثبيت
السيناريو 3: لوحة تحكم إدارية
- العروض: إدارة المستخدمين، التقارير، الإعدادات
- الميزات: User Management، Theming، User Messages
- الفائدة: صلاحيات دقيقة لكل مستخدم
سادسًا: نظام الوحدات (Modules)
البنية
كل وحدة (Module) هي مجلد مستقل يحتوي على:
text
/modules/
/my-module/
├── controller/ ← منطق PHP
├── view/ ← عروض HTML5
├── js/ ← تفاعلية JavaScript
├── dao/ ← فئات الوصول للبيانات
├── menu.php ← تعريف قائمة التنقل
└── config.php ← الإعدادات
وحدات جاهزة على GitHub
- Email/SMS Sender — إرسال الإشعارات
- File Uploader — رفع الصور والمستندات
- Session Monitor — مراقبة الجلسات
- Data Tracker — تتبع التغييرات (Audit Log)
- Menu Panels — لوحات إضافية في الصفحة الرئيسية
- وحدات أخرى…
بناء وحدة مخصصة
- أنشئ المجلد بالبنية أعلاه
- عرّف العروض والمتحكمات
- أضف تعريف القائمة في
menu.php - سجّل الوحدة في إعدادات التطبيق
- لا حاجة لتعديل نواة الإطار — الوحدات معزولة
سابعًا: الأمان في ZnetDK
طبقات الحماية
- Authentication — تسجيل دخول مدمج
- Authorization — صلاحيات حسب الملف الشخصي (Profile)
- Validation — تحقق من المدخلات في الخادم
- DAO — حماية من SQL Injection
- Menu Control — إخفاء الصفحات غير المسموحة
- Session Management — إدارة الجلسات
أفضل الممارسات
- استخدم HTTPS دائمًا
- لا تُخزّن كلمات المرور كنص صريح (ZnetDK يتعامل معها)
- راجع صلاحيات كل مستخدم دوريًا
- سجّل الأحداث الحساسة (عبر وحدة Data Tracker)
ثامنًا: نصائح عملية للمطوّر
للمبتدئين
- ابدأ بتطبيق Hello World بسيط
- افهم
menu.phpأولًا — فهو خريطة التطبيق - تدرّب على Data Lists قبل User Forms
- استخدم الوحدات الجاهزة بدلًا من إعادة اختراع العجلة
للمتقدمين
- اكتب وحداتك الخاصة لتغليف منطق العمل المتكرر
- استفد من Full-Stack API لتقليل الكود
- حسّن الاستعلامات باستخدام DAO
- أضف Caching للبيانات الثابتة
أخطاء شائعة
- ❌ وضع منطق الأعمال في العروض
- ❌ إهمال التحقق من البيانات في الخادم
- ❌ تخزين بيانات حساسة في Local Storage
- ❌ عدم استخدام الوحدات → كود متشابك
تاسعًا: خارطة تعلّم مقترحة
| الأسبوع | الموضوع |
|---|---|
| 1 | تثبيت ZnetDK + Hello World + فهم MVC |
| 2 | menu.php + Navigation + Theming |
| 3 | Data Lists + DAO + SQL |
| 4 | User Forms + Validation |
| 5 | Authentication + User Management |
| 6 | PWA + Local Storage + AJAX |
| 7 | Modules + وحدات مخصصة |
| 8 | مشروع كامل متكامل |
عاشرًا: المصادر الرسمية
- الموقع: ZnetDK.com
- GitHub: مستودع المشروع + الوحدات
- الوثائق: Documentation on Modules
- الترخيص: GPL — راجع الإشعارات القانونية
- الفترة: 2014–2026 (مشروع مستمر ومستقر)
خلاصة نهائية
ZnetDK 4 Mobile ليس مجرد إطار عمل — بل منصة كاملة لبناء تطبيقات الأعمال على الويب. قوّته في:
| الجانب | القيمة المضافة |
|---|---|
| السرعة | تطبيق كامل في دقائق |
| الاكتمال | كل الميزات المطلوبة جاهزة |
| البساطة | معايير ويب مفتوحة |
| التوسع | وحدات معزولة قابلة للتركيب |
| الأمان | طبقات حماية متعددة |
| المجانية | GPL — بدون تكلفة |
هل تريد أن أتوسّع في جانب معيّن؟ مثلًا:
- مثال كود كامل لتطبيق بسيط
- شرح تفصيلي لـ PWA أو DAO
- دليل تثبيت خطوة بخطوة
- ترجمة الوثائق الرسمية
- مقارنة أعمق مع إطار محدد
أخبرني بما يهمّك وسأعمّق فيه.
— تعمّق أعمق في ZnetDK 4 Mobile
سأأخذك الآن إلى مستوى أكثر تفصيلًا: بنية الكود الفعلية، أمثلة عملية، دورة حياة الطلب، وأسرار الاستخدام المتقدم. هذا المحتوى موجّه لمن يريد الانتقال من الفهم إلى التنفيذ.
أولًا: دورة حياة الطلب (Request Lifecycle) بالتفصيل
لفهم ZnetDK حقًا، يجب أن تعرف ما يحدث لحظة ضغط المستخدم على زر حتى ظهور النتيجة:
text
1. المستخدم يضغط زر "حفظ" في نموذج HTML5
↓
2. JavaScript Controller يلتقط الحدث (Event)
↓
3. يتم تجميع البيانات في كائن JSON
↓
4. طلب AJAX يُرسل إلى الخادم (POST)
↓
5. ZnetDK Router يستقبل الطلب ويوجّهه للمتحكم الصحيح
↓
6. PHP Controller Action يُنفّذ
↓
7. Validation Class يتحقق من صحة البيانات
├── إذا فشل → إرجاع رسالة خطأ (User Message)
└── إذا نجح → المتابعة
↓
8. DAO Class ينفّذ INSERT/UPDATE/DELETE
↓
9. استجابة JSON تعود للعميل
↓
10. JavaScript يعرض Snackbar نجاح + يحدّث القائمة
الفائدة من فهم هذا: تعرف أين تضع الكود بالضبط، وكيف تُشخّص الأخطاء.
ثانيًا: بنية ملفات تطبيق كامل
الهيكل العام
text
/myapp/ │ ├── index.php ← نقطة الدخول الوحيدة (SPA) ├── menu.php ← تعريف قائمة التنقل ├── config.php ← إعدادات التطبيق العامة │ ├── /app/ │ ├── /controller/ ← متحكمات PHP │ │ ├── ProductController.php │ │ └── CustomerController.php │ │ │ ├── /view/ ← عروض HTML5 │ │ ├── product-list.php │ │ ├── product-form.php │ │ └── customer-list.php │ │ │ ├── /js/ ← JavaScript مخصص │ │ ├── product.js │ │ └── customer.js │ │ │ ├── /dao/ ← فئات الوصول للبيانات │ │ ├── ProductDAO.php │ │ └── CustomerDAO.php │ │ │ └── /validator/ ← قواعد التحقق │ └── ProductValidator.php │ ├── /modules/ ← الوحدات الإضافية │ ├── /email-sender/ │ ├── /file-uploader/ │ └── /data-tracker/ │ ├── /css/ ← تخصيص الثيم │ └── custom-theme.css │ ├── /images/ │ └── logo.png │ └── /engine/ ← نواة ZnetDK (لا تُعدّلها)
قاعدة ذهبية: لا تعدّل /engine/ أبدًا — كل تخصيصاتك في /app/ و/modules/.
ثالثًا: أمثلة كود كاملة
مثال 1: تعريف قائمة التنقل — menu.php
php
<?php
$menu = array(
array(
'label' => 'الرئيسية',
'view' => 'home',
'icon' => 'home'
),
array(
'label' => 'المنتجات',
'items' => array(
array('label' => 'قائمة المنتجات', 'view' => 'product-list'),
array('label' => 'إضافة منتج', 'view' => 'product-form')
)
),
array(
'label' => 'العملاء',
'items' => array(
array('label' => 'قائمة العملاء', 'view' => 'customer-list')
)
)
);
مثال 2: فئة DAO — ProductDAO.php
php
<?php
class ProductDAO extends \ZnetDK\DAO {
protected function getTableName() {
return 'products';
}
protected function getPrimaryKey() {
return 'product_id';
}
protected function getColumns() {
return array('product_id', 'name', 'price', 'stock', 'created_at');
}
// دالة مخصصة: جلب المنتجات منخفضة المخزون
public function getLowStock($threshold = 10) {
return $this->getAll(
"stock < $threshold",
"stock ASC"
);
}
}
مثال 3: متحكم PHP — ProductController.php
php
<?php
class ProductController extends \ZnetDK\Controller {
// إجراء: حفظ منتج جديد
public function action_save() {
$data = $this->getRequestData();
// 1. التحقق
$validator = new ProductValidator();
if (!$validator->validate($data)) {
$this->setError($validator->getErrors());
return;
}
// 2. التخزين عبر DAO
$dao = new ProductDAO();
$id = $dao->save($data);
// 3. الاستجابة
$this->setSuccess("تم حفظ المنتج رقم $id بنجاح");
}
// إجراء: جلب قائمة المنتجات
public function action_list() {
$dao = new ProductDAO();
$products = $dao->getAll(null, 'name ASC');
$this->setResponse($products);
}
}
مثال 4: عرض HTML5 — product-list.php
html
<div class="w3-container">
<h2>قائمة المنتجات</h2>
<!-- حقل بحث مدمج -->
<input type="search" id="product-search"
placeholder="ابحث عن منتج..."
class="w3-input w3-border">
<!-- جدول البيانات -->
<table id="product-table" class="w3-table w3-striped">
<thead>
<tr>
<th data-sort="name">الاسم</th>
<th data-sort="price">السعر</th>
<th data-sort="stock">المخزون</th>
<th>إجراءات</th>
</tr>
</thead>
<tbody><!-- يُملأ عبر AJAX --></tbody>
</table>
<!-- أزرار الإجراءات الثابتة -->
<div class="znetdk-action-buttons">
<button onclick="Product.add()">➕ إضافة</button>
<button onclick="Product.refresh()">🔄 تحديث</button>
<button onclick="ZnetDK.scrollTop()">⬆️ أعلى</button>
</div>
</div>
مثال 5: JavaScript — product.js
javascript
var Product = {
// تحميل القائمة
load: function() {
ZnetDK.request({
controller: 'ProductController',
action: 'list',
success: function(products) {
Product.renderTable(products);
}
});
},
// عرض البيانات في الجدول
renderTable: function(products) {
var html = '';
products.forEach(function(p) {
html += '<tr>';
html += '<td>' + p.name + '</td>';
html += '<td>' + p.price + '</td>';
html += '<td>' + p.stock + '</td>';
html += '<td><button onclick="Product.edit(' + p.product_id + ')">تعديل</button></td>';
html += '</tr>';
});
$('#product-table tbody').html(html);
},
// تحديث
refresh: function() {
Product.load();
ZnetDK.showMessage('تم التحديث', 'info');
},
// إضافة
add: function() {
ZnetDK.showView('product-form');
},
// تعديل
edit: function(id) {
ZnetDK.showView('product-form', { id: id });
}
};
// تحميل تلقائي عند فتح العرض
$(document).ready(function() {
Product.load();
});
رابعًا: دورة حياة الجلسة والأمان
كيف يتعامل ZnetDK مع تسجيل الدخول؟
text
1. المستخدم يفتح التطبيق
↓
2. ZnetDK يتحقق من وجود جلسة نشطة
├── موجودة → الدخول مباشرة
└── غير موجودة → عرض Login Form
↓
3. المستخدم يُدخل بياناته
↓
4. التحقق في الخادم (كلمة المرور مشفّرة)
↓
5. إنشاء جلسة + تحميل الصلاحيات (Profile)
↓
6. بناء قائمة التنقل حسب صلاحيات المستخدم
↓
7. تحميل العرض الأول
مستويات الصلاحيات
| المستوى | الوصف |
|---|---|
| View | رؤية الصفحة فقط |
| Create | إضافة سجلات جديدة |
| Update | تعديل السجلات |
| Delete | حذف السجلات |
| Admin | كل ما سبق + إدارة المستخدمين |
كيف تُخفي صفحة عن مستخدم؟
php
// في menu.php
$menu = array(
array(
'label' => 'التقارير',
'view' => 'reports',
'profile' => 'manager' // تظهر فقط لمن ملفه "manager"
)
);
خامسًا: PWA — التفاصيل التقنية
المكونات الثلاثة الأساسية
1. Manifest — manifest.json
json
{
"name": "تطبيق المنتجات",
"short_name": "منتجات",
"start_url": "/index.php",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"icons": [
{ "src": "/images/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/images/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
2. Service Worker — sw.js
javascript
var CACHE_NAME = 'znetdk-v1';
var URLS_TO_CACHE = [
'/',
'/index.php',
'/css/custom-theme.css',
'/images/logo.png',
'/offline.html'
];
// التثبيت: تخزين الملفات الأساسية
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(URLS_TO_CACHE);
})
);
});
// الجلب: من الكاش أولًا، ثم الشبكة
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request).catch(function() {
return caches.match('/offline.html');
});
})
);
});
3. Offline Page — offline.html
html
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>غير متصل</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
</head>
<body class="w3-container w3-center" style="padding-top:50px">
<h1>📡</h1>
<h2>لا يوجد اتصال بالإنترنت</h2>
<p>يرجى التحقق من اتصالك والمحاولة مرة أخرى.</p>
<button onclick="location.reload()" class="w3-button w3-blue">
إعادة المحاولة
</button>
</body>
</html>
استراتيجيات التخزين المؤقت (Caching Strategies)
| الاستراتيجية | متى تستخدمها |
|---|---|
| Cache First | الأصول الثابتة (CSS, JS, صور) |
| Network First | البيانات الحيّة (API) |
| Stale-While-Revalidate | محتوى يتغير أحيانًا |
| Cache Only | صفحة Offline |
سادسًا: Local Storage — أفضل الممارسات
ما الذي يجب تخزينه؟
✅ نعم:
- تفضيلات الواجهة (اللغة، الثيم)
- مسودات النماذج غير المكتملة
- آخر صفحة تمت زيارتها
- رموز التخزين المؤقت غير الحساسة
❌ لا:
- كلمات المرور
- رموز الجلسة الحساسة
- بيانات شخصية حساسة (رقم الهوية، البطاقة)
- أي شيء يُعدّ اختراقه كارثة
مثال عملي
javascript
// حفظ تفضيلات المستخدم
var Preferences = {
save: function(key, value) {
localStorage.setItem('pref_' + key, JSON.stringify(value));
},
load: function(key, defaultValue) {
var val = localStorage.getItem('pref_' + key);
return val ? JSON.parse(val) : defaultValue;
}
};
// الاستخدام
Preferences.save('theme', 'dark');
var theme = Preferences.load('theme', 'light');
سابعًا: التوسع والأداء
متى يصبح التطبيق بطيئًا؟
- قوائم ضخمة (آلاف السجلات في صفحة واحدة)
- استعلامات SQL غير محسّنة (بدون فهارس)
- طلبات AJAX متكررة دون تخزين مؤقت
- صور كبيرة دون ضغط
الحلول
| المشكلة | الحل |
|---|---|
| قوائم ضخمة | Pagination — عرض 20-50 سجلًا |
| استعلامات بطيئة | إضافة Indexes في MySQL |
| طلبات متكررة | Caching في Local Storage |
| صور كبيرة | ضغط + صيغة WebP |
| تحميل بطيء | Lazy Loading للصور |
مثال Pagination
php
public function action_list() {
$page = $this->getParam('page', 1);
$perPage = 25;
$offset = ($page - 1) * $perPage;
$dao = new ProductDAO();
$total = $dao->count();
$products = $dao->getAll(null, 'name ASC', $offset, $perPage);
$this->setResponse(array(
'data' => $products,
'total' => $total,
'page' => $page,
'pages' => ceil($total / $perPage)
));
}
ثامنًا: التشخيص وحل المشكلات
أدوات التشخيص
- Console في المتصفح → أخطاء JavaScript
- Network Tab → طلبات AJAX وردودها
- Application Tab → Local Storage + Service Worker
- PHP Error Log → أخطاء الخادم
- MySQL Slow Query Log → استعلامات بطيئة
أخطاء شائعة وحلولها
| الخطأ | السبب | الحل |
|---|---|---|
404 على view | مسار خاطئ في menu.php | تحقق من المسار |
AJAX fails | صلاحيات أو جلسة منتهية | أعد تسجيل الدخول |
DAO error | أسماء أعمدة خاطئة | راجع getColumns() |
Validation fails | قواعد صارمة جدًا | عدّل القواعد |
PWA لا يعمل | Service Worker غير مسجّل | تحقق من HTTPS |
تاسعًا: نصائح احترافية متقدمة
1. تنظيم الكود
- وحدة واحدة لكل وظيفة — لا تخلط المنتجات بالعملاء
- أسماء واضحة —
getActiveProductsأفضل منgetData - تعليقات موجزة — اشرح “لماذا” لا “ماذا”
2. الأمان المتقدم
- CSRF Tokens — ZnetDK يتعامل معها تلقائيًا
- Rate Limiting — حدّد عدد الطلبات لكل مستخدم
- Input Sanitization — نظّف المدخلات دائمًا
- HTTPS إلزامي — خاصًة مع PWA
3. تجربة المستخدم
- رسائل واضحة — “تم الحفظ” أفضل من “OK”
- مؤشرات تحميل — Spinner أثناء AJAX
- تأكيد قبل الحذف — لا تحذف مباشرة
- حفظ تلقائي — للمسودات في Local Storage
4. الصيانة
- Git — استخدم فروعًا لكل ميزة
- Backups — نسخ احتياطية دورية
- Logging — سجّل الأحداث المهمة
- Documentation — وثّق قراراتك
عاشرًا: خارطة طريق من الصفر إلى الإنتاج
| المرحلة | المهام | المدة |
|---|---|---|
| 1. التأسيس | تثبيت + Hello World + فهم MVC | 2-3 أيام |
| 2. التنقل | menu.php + Views + Theming | 3-4 أيام |
| 3. البيانات | DAO + SQL + Data Lists | أسبوع |
| 4. الإدخال | Forms + Validation | 4-5 أيام |
| 5. الأمان | Auth + Users + Profiles | 3-4 أيام |
| 6. التقدم | PWA + Offline + Local Storage | أسبوع |
| 7. التوسع | Modules + Custom Modules | أسبوع |
| 8. الإنتاج | اختبار + تحسين + نشر | أسبوع |
الإجمالي: 6-8 أسابيع لمطوّر واحد لتطبيق كامل.
الحادي عشر: المصادر والتعلّم المستمر
داخل ZnetDK
/engine/— اقرأ الكود المصدري (أفضل مرجع)- الوثائق الرسمية على ZnetDK.com
- مستودع GitHub — الوحدات الجاهزة
- أمثلة التطبيقات المرفقة
خارج ZnetDK
- W3.CSS — w3schools.com
- jQuery — api.jquery.com
- PHP PDO — php.net
- PWA — web.dev/progressive-web-apps
- MySQL — dev.mysql.com/doc
نصائح التعلّم
- تعلّم بالمشروع — ابنِ شيئًا حقيقيًا
- اقرأ الكود — أفضل من قراءة الكتب
- جرّب — لا تخف من الأخطاء
- شارك — اسأل في المجتمعات
- وثّق — اكتب ما تعلّمته
خلاصة نهائية موسّعة
ZnetDK 4 Mobile هو:
- ✅ إطار عمل كامل — ليس مجرد مكتبة
- ✅ تطبيق جاهز — تبدأ من نقطة متقدمة
- ✅ معايير مفتوحة — لا قيود تقنية
- ✅ آمن — طبقات حماية متعددة
- ✅ قابل للتوسع — نظام وحدات مرن
- ✅ مجاني — GPL بدون تكلفة
- ✅ مستقر — 2014-2026
متى تختاره؟
- تطبيق أعمال داخلي
- فريق صغير / مطوّر واحد
- تسليم سريع
- ميزانية محدودة
- تطبيق يعمل على الهاتف
متى تتجنّبه؟
- تطبيق عام ضخم (ملايين المستخدمين)
- حاجة لتقنيات غير PHP
- تكامل معقد مع أنظمة أخرى
- فريق يفضّل إطارًا آخر
هل تريد الآن:
- مشروعًا كاملًا بخطوات تنفيذية (تطبيق إدارة مهام مثلًا)؟
- شرحًا مرئيًا لتدفق البيانات؟
- مقارنة تفصيلية مع Laravel بمثال كود؟
- ترجمة وثيقة رسمية معيّنة؟
- حل مشكلة محددة تواجهها؟
أخبرني بالاتجاه الذي تريده وسأعمّق فيه أكثر