لینک‌های مفید برای شرکت در مسابقه:

در زمان مسابقه می‌توانید سوال‌های خود را از قسمت "سوال بپرسید" مطرح کنید.

سری سوم راهنمایی‌ها به سؤالات اضافه شد.

پسر J


مهدی طرفدار پروژه‌های خاص و سخت است. اخیراً یکی از دوستان صمیمی او، نیما، سفارش نوشتن یک سایت تبلیغاتی را به او داده است. از آن‌جا که مهدی نمایندگی فروش هاست و سرور ندارد، از نیما درخواست یک هاست برای میزبانی پروژه کرد. نیما هم این درخواست را با کمال میل قبول کرد و دسترسی یک هاست اشتراکی ساده با کم‌ترین امکانات ممکن را برای مهدی فرستاد. مهدی پس از بررسی‌های فراوان، متوجه شد که این هاست حتی قابلیت اتصال به MySQL را هم ندارد! لذا تصمیم گرفت که خودش اطلاعات را در قالب JSON درون فایل‌های مختلف نگه‌داری کند و از آن‌ها استفاده کند.

مهدی فرصت کمی برای انجام این پروژه دارد؛ بنابراین از شما می‌خواهیم که بخش ذخیره‌سازی اطلاعات را برای مهدی بنویسید.

جزئیات پروژه🔗

ساختار فایل‌هایی که اطلاعات در آن‌ها ذخیره می‌شوند به صورت زیر است:

db_files
├── table1.json
├── table2.json
└── table3.json
Plain text

همه‌ی جداول دیتابیس در یک دایرکتوری مشخص قرار می‌گیرند. نام هر فایل، نمایانگر نام جدول است. در مثال بالا، سه جدول با نام‌های table1، table2 و table3 وجود دارد.

هر جدول شامل خانه‌ای به نام schema است و مشخصات مربوط به ستون‌های جدول در آن قرار دارند. در schema هر جدول، مقدار پیش‌فرض ستون‌ها و امکان null بودن آن‌ها تعریف می‌شود. مقدار پیش‌فرض هر ستون در خانه‌ای به نام default و امکان null بودن مقدار ستون به صورت boolean در خانه‌ای به نام nullable ذخیره می‌شود. لزوماً خانه‌ی default برای ستون تعریف نمی‌شود، امّا خانه‌ی nullable برای تمامی ستون‌ها موجود است.

مثالی از schema یک جدول:🔗

{
    "first_name": {
        "nullable": false
    },
    "last_name": {
        "nullable": true
    },
    "country": {
        "nullable": true,
        "default": "Iran"
    }
}
JSON

هم‌چنین هر جدول خانه‌ای به نام data دارد که شامل آرایه‌ای از سطرهای موجود در جدول است.

مثالی از یک جدول:🔗

{
    "schema": {
        "first_name": {
            "nullable": false
        },
        "last_name": {
            "nullable": true
        },
        "country": {
            "nullable": true,
            "default": "Iran"
        }
    },
    "data": [{
        "first_name": "John",
        "last_name": "Smith",
        "country": "U.S."
    }, {
        "first_name": "Ali",
        "last_name": null,
        "country": "Iran"
    }]
}
JSON

کل پروژه را در قالب کلاسی به نام JsonDB با موارد خواسته‌شده‌ی زیر پیاده‌سازی کنید:

  • متد __construct را به گونه‌ای پیاده‌سازی کنید که مسیر اصلی ذخیره‌سازی فایل‌های جداول را دریافت کند. در صورتی که آدرسی به متد داده نشد، مسیر فعلی اسکریپت به‌عنوان مکان ذخیره‌سازی فایل‌های جداول در نظر گرفته می‌شود.

  • متد insert را به گونه‌ای پیاده‌سازی کنید که نام جدول و آرایه‌ای از ستون‌های سطر موردنظر را دریافت کرده و آن را به انتهای جدول اضافه کند. در صورتی که مقداری برای ستون خاصی تعریف نشود و مقدار پیش‌فرضی برای آن ستون وجود نداشته باشد و امکان null بودن ستون وجود نداشته باشد، باید یک Exception با پیام No value provided for column column_name (column_name نام ستون است) throw شود. در صورتی که مقداری برای ستون خاصی تعریف نشود و مقدار پیش‌فرضی برای آن ستون وجود نداشته باشد و امکان null بودن مقدار ستون وجود داشته باشد، مقدار null به عنوان مقدار ستون ذخیره خواهد شد. در صورتی که ستونی به متد داده شود و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد insert:🔗

$db = new JsonDB(__DIR__ . '/db');

$db->insert('users', ['first_name' => 'Ali', 'last_name' => 'AliZadeh', 'country' => 'Iran']);

$db->insert('users', ['first_name' => 'Ali', 'last_name' => 'AliZadeh']);

$db->insert('users', ['last_name' => 'AliZadeh', 'country' => 'Iran']); // Exception: No value provided for column first_name
PHP
  • متد select را به گونه‌ای پیاده‌سازی کنید که نام جدول و آرایه‌ای از ستون‌ها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستون‌هایشان با مقادیر ستون‌های ورودی یکسان است را در قالب آرایه برگرداند. لزوماً مقادیر همه‌ی ستون‌ها به‌عنوان ورودی به متد داده نمی‌شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرهای خروجی AND وجود دارد. در صورتی که ستونی به‌عنوان ورودی به متد داده نشود، باید همه‌ی سطرها به‌عنوان خروجی تابع return شود. در صورتی که ستونی به متد داده شود و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد select:🔗

$db = new JsonDB(__DIR__ . '/db');

$db->select('users');
/*
[
    [
        'first_name' => 'Ali',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->select('users', ['first_name' => 'Ali']);
/*
[
    [
        'first_name' => 'Ali',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ]
]
*/

$db->select('users', ['first_name' => 'Ali', 'country' => null]);
/*
[]
*/

$db->insert('users', ['invalid_column' => 'Sample Value', 'country' => 'Iran']); // Exception: Column invalid_column not found
PHP
  • متد update مقدار یک یا چند ستون را در سطرهایی که مشخص می‌کنیم تغییر می‌دهد. این متد شامل سه آرگومان بوده که آرگومان اول نام جدول موردنظر برای به‌روزرسانی است، آرگومان دوم شامل مقادیر جدید ستون‌هایی است که قرار است تغییر کنند و آرگومان سوم مشخص می‌کند که ستون‌های چه سطرهایی باید تغییر کنند؛ به طوری که هر سطری که مقادیر ستون‌هایش برابر با مقادیر ستون‌های آرگومان سوم باشد باید به‌روزرسانی شود. لزوماً مقادیر همه‌ی ستون‌های جدول در آرگومان‌های دوم و سوّم موجود نیستند. اگر آرگومان سوّم به متد داده نشد، باید همه‌ی سطرهای جدول به‌روزرسانی شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرها جهت به‌روزرسانی AND وجود دارد. در صورتی که ستونی در آرگومان دوم یا سوّم موجود باشد و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد update:🔗

$db = new JsonDB(__DIR__ . '/db');

$db->select('users');
/*
[
    [
        'first_name' => 'Ali',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->update('users', ['first_name' => 'Mohammad'], ['first_name' => 'Ali']);

$db->select('users');
/*
[
    [
        'first_name' => 'Mohammad',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->update('users', ['first_name' => 'Mohammad']);

$db->select('users');
/*
[
    [
        'first_name' => 'Mohammad',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'Mohammad',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->update('users', ['invalid_column' => 'Test']); // Exception: Column invalid_column not found
PHP
  • متد delete را به گونه‌ای پیاده‌سازی کنید که نام جدول و آرایه‌ای از ستون‌ها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستون‌هایشان با مقادیر ستون‌های ورودی یکسان است را از جدول حذف کند. لزوماً مقادیر همه‌ی ستون‌ها به‌عنوان ورودی به متد داده نمی‌شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرها برای حذف AND وجود دارد. در صورتی که ستونی به‌عنوان ورودی به متد داده نشود، باید همه‌ی سطرها از جدول حذف شوند. در صورتی که ستونی به متد داده شود و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد delete:🔗

$db = new JsonDB(__DIR__ . '/db');

$db->select('users');
/*
[
    [
        'first_name' => 'Ali',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->delete('users', ['first_name' => 'Mohammad']);

$db->select('users');
/*
[
    [
        'first_name' => 'Ali',
        'last_name' => 'AliZadeh',
        'country' => 'Iran'
    ],
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->delete('users', ['first_name' => 'Ali']);

$db->select('users');
/*
[
    [
        'first_name' => 'John',
        'last_name' => 'Smith',
        'country' => null
    ]
]
*/

$db->delete('users');

$db->select('users');
/*
[]
*/

$db->delete('users', ['invalid_column' => 'Test']); // Exception: Column invalid_column not found
PHP

نکته: در صورتی که جدول موردنظر در هر یک از متدهای insert، select، update یا delete یافت نشود، باید یک Exception با پیام Table table_name not found (table_name نام جدول موردنظر است) throw شود.

آن‌چه باید آپلود کنید🔗

یک فایل PHP که کلاس JsonDB درون آن قرار دارد آپلود کنید.


راهنمایی ۱

بدیهی است که باید یک property برای ذخیره‌سازی محل قرارگیری فایل‌های جدول‌ها در نظر بگیریم (آن را $db_path می‌نامیم). بنابراین، متد __construct به‌صورت زیر خواهد بود:

<?php

class JsonDB
{
    private $db_path;

    public function __construct($db_path = __DIR__)
    {
        $this->db_path = $db_path;
    }
}
PHP

اگر نام یک جدول برابر با $table_name باشد، مسیر فایل جدول برابر خواهد بود با:

$table_filepath = $this->db_path . "/" . $table_name . ".json";
PHP

برای بررسی وجود فایل جدول، می‌توان از تابع file_exists یا is_file استفاده کرد. در صورت عدم وجود فایل، باید یک Exception throw کرد:

if (!file_exists($table_filepath)) {
    throw new Exception("Table $table_filepath not found");
    return false;
}
PHP

علت بازگشت مقدار false، جلوگیری از ادامه‌ی روند اجرای متد در صورت استفاده از try/catch است.

برای دریافت محتویات فعلی جدول، می‌توان از توابع file_get_contents و json_decode استفاده کرد:

$table = json_decode(file_get_contents($table_filepath), true);
PHP

برای بررسی مطابقت ستون‌های جدول با ستون‌های ورودی، می‌توان از حلقه‌ی foreach استفاده کرد:

$schema = $table["schema"];
foreach ($columns as $key => $value) {
    if (!isset($schema[$key])) {
        throw new Exception("Column $key not found");
        return false;
    }
}
PHP

برای بررسی nullable بودن ستون‌ها و مقدار پیش‌فرض ستون‌ها نیز می‌توان از حلقه‌ی foreach استفاده کرد. در حین پیمایش روی ستون‌ها، اگر مطابقت بین جدول و ستون‌های ورودی وجود داشته باشد، مقدار ستون ورودی را درون یک آرایه‌ی موقت نگه می‌داریم و در غیر این‌صورت، یک Exception throw می‌کنیم:

$row = [];
foreach ($schema as $column_name => $attributes) {
    if (
        (
            !isset($columns[$column_name])
            && !$attributes["nullable"]
            && !isset($attributes["default"])
        ) || (
            isset($columns[$column_name])
            && $columns[$column_name] === null
            && !$attributes["nullable"]
        )
    ) {
        throw new Exception("No value provided for column $column_name");
        return false;
    }
    if (isset($columns[$column_name])) {
        $row[$column_name] = $columns[$column_name];
    }
    else {
        if (isset($attributes["default"])) {
            $row[$column_name] = $attributes["default"];
        } else {
            $row[$column_name] = null;
        }
    }
}
PHP

در نهایت، آرایه‌ی $row را به انتهای data جدول اضافه کرده و با استفاده از تابع file_put_contents و json_encode، نتیجه را در فایل ذخیره می‌کنیم:

$table["data"][] = $row;
file_put_contents($table_filepath, json_encode($table));
PHP
راهنمایی ۲

برای پیاده‌سازی متد select، پس از بررسی وجود جدول، آرگومان دوم را بررسی می‌کنیم. اگر این آرایه خالی باشد، تمامی سطرهای جدول را برمی‌گردانیم. در غیر این‌صورت، schema جدول را با آرگومان دوم متد مقایسه می‌کنیم. اگر ستون نامعتبری یافت شود، یک Exception با پیغام Column column_name not found throw می‌کنیم:

$table_filepath = $this->db_path . '/' . $table_name . '.json';
if (!file_exists($table_filepath)) {
    throw new Exception("Table $table_name not found");
    return false;
}
$table = json_decode(file_get_contents($table_filepath), true);
if (empty($columns)) {
    return $table['data'];
}
$schema = $table['schema'];
foreach ($columns as $key => $value) {
    if (!isset($schema[$key])) {
        throw new Exception("Column $key not found");
        return false;
    }
}
PHP

در ادامه، سطرهای موجود در جدول را با آرگومان دوم مقایسه می‌کنیم. اگر ستونی یافت شود که مقدار آن با آرگومان دوم مغایرت داشته باشد، از آن صرف‌نظر می‌کنیم. کد این قسمت به‌صورت زیر خواهد بود:

$rows = [];
foreach ($table['data'] as $table_row) {
    foreach ($columns as $key => $value) {
        if ($table_row[$key] != $value) {
            continue 2;
        }
    }
    $rows[] = $table_row;
}
PHP

در نهایت، آرایه‌ی $rows را برمی‌گردانیم.

راهنمایی ۳

برای پیاده‌سازی متد delete نیز ابتدا صحت نام جدول و ستون‌ها را بررسی می‌کنیم:

$table_filepath = $this->db_path . '/' . $table_name . '.json';
if (!file_exists($table_filepath)) {
    throw new Exception("Table $table_name not found");
    return false;
}
$table = json_decode(file_get_contents($table_filepath), true);
$schema = $table['schema'];
foreach ($conditions as $key => $value) {
    if (!isset($schema[$key])) {
        throw new Exception("Column $key not found");
        return false;
    }
}
PHP

برای حذف سطرهای موردنظر از جدول، می‌توان از تابع array_filter استفاده کرد. اگر مقادیر ستون‌های موجود در آرگومان دوم متد با ستون‌های یک سطر مطابقت داشته باشد، آن سطر از ستون حذف می‌شود.

در پایان، برای ریست کردن keyهای جدول، از تابع array_values استفاده کرده و نتیجه را در فایل جدول ذخیره می‌کنیم:

$table['data'] = array_values(
    array_filter($table['data'], function ($row) use ($conditions) {
        foreach ($conditions as $key => $value) {
            if ($row[$key] != $value) {
                return true;
            }
        }
        return false;
    })
);
file_put_contents($table_filepath, json_encode($table));
PHP
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.