برای آشنایی بیشتر شما با نحوهی کار با سیستم داوری کوئرا و قابلیتهای آن برای اجرای سؤالات PHP میتوانید از این ویدئو نیز کمک بگیرید. همچنین این سوال به عنوان نمونه برای شما حل میشود.
%video.aparat_yZ49A%
بر اساس آمار گزارششده، میانگین سرعت مطالعهی افراد در اینترنت بین ۲۰۰ تا ۲۵۰ واژه در هر دقیقه است. احتمالاً وبلاگهایی را دیدهاید که در آنها زمان تقریبی مطالعهی هر پست زیر عنوان آن نوشته شده است. از شما میخواهیم این قابلیت را پیادهسازی کنید.
# جزئیات پروژه
تابعی با نام `estimateReadingTime` پیادهسازی کنید که یک رشته از کاربر دریافت کرده و زمان مطالعهی پست را برحسب دقیقه برگرداند. این تابع باید تعداد کلمات موجود در متن را تقسیم بر ۲۰۰ کرده و سقف آن را برگرداند. همچنین، کاراکترهای `.`، `?`، `!`، `,`، `;` و `:` باید نادیده گرفته شوند. کلمات با یک یا چند کاراکتر `' '`، `'\t'` یا `'\n'` از یکدیگر جدا شدهاند. همچنین، تفاوتی برای طول کلمات قائل نمیشویم.
امضای تابع `estimateReadingTime` بهصورت زیر خواهد بود:
```php
function estimateReadingTime(string $text): int
{
// TODO: Implement
}
```
# مثال
```php
<?php
function estimateReadingTime(string $text): int
{
// TODO: Implement
}
function randomText(int $wordsCount): string
{
$words = [];
for ($i = 0; $i < $wordsCount; $i++) {
$words[] = randomWord();
}
$text = implode(" ", $words);
return $text;
}
function randomWord(): string
{
$vowels = ["a","e","i","o","u"];
$consonants = [
"b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "r", "s", "t", "v", "w", "x", "y", "z"
];
$string = "";
$max = intdiv(rand(4, 8), 2);
for ($i = 1; $i <= $max; $i++) {
$string .= $consonants[array_rand($consonants)];
$string .= $vowels[array_rand($vowels)];
}
return $string;
}
echo estimateReadingTime(randomText(200)); // 1
echo "\n";
echo estimateReadingTime(randomText(201)); // 2
echo "\n";
echo estimateReadingTime(randomText(50)); // 1
echo "\n";
echo estimateReadingTime(randomText(1895)); // 10
```
خروجی موردانتظار:
```
1
2
1
10
```
# آنچه باید آپلود کنید
یک فایل *PHP* که تابع `estimateReadingTime` در آن پیادهسازی شده است آپلود کنید.
مهدی طرفدار پروژههای خاص و سخت است. اخیراً یکی از دوستان صمیمی او، نیما، سفارش نوشتن یک سایت تبلیغاتی را به او داده است. از آنجا که مهدی نمایندگی فروش هاست و سرور ندارد، از نیما درخواست یک هاست برای میزبانی پروژه کرد. نیما هم این درخواست را با کمال میل قبول کرد و دسترسی یک هاست اشتراکی ساده با کمترین امکانات ممکن را برای مهدی فرستاد. مهدی پس از بررسیهای فراوان، متوجه شد که این هاست حتی قابلیت اتصال به _MySQL_ را هم ندارد! لذا تصمیم گرفت که خودش اطلاعات را در قالب _JSON_ درون فایلهای مختلف نگهداری کند و از آنها استفاده کند.
مهدی فرصت کمی برای انجام این پروژه دارد؛ بنابراین از شما میخواهیم که بخش ذخیرهسازی اطلاعات را برای مهدی بنویسید.
# جزئیات پروژه
ساختار فایلهایی که اطلاعات در آنها ذخیره میشوند به صورت زیر است:
```
db_files
├── table1.json
├── table2.json
└── table3.json
```
همهی جداول دیتابیس در یک دایرکتوری مشخص قرار میگیرند. نام هر فایل، نمایانگر نام جدول است. در مثال بالا، سه جدول با نامهای `table1`، `table2` و `table3` وجود دارد.
هر جدول شامل خانهای به نام `schema` است و مشخصات مربوط به ستونهای جدول در آن قرار دارند. در `schema` هر جدول، مقدار پیشفرض ستونها و امکان `null` بودن آنها تعریف میشود. مقدار پیشفرض هر ستون در خانهای به نام `default` و امکان `null` بودن مقدار ستون به صورت `boolean` در خانهای به نام `nullable` ذخیره میشود. لزوماً خانهی `default` برای ستون تعریف نمیشود، امّا خانهی `nullable` برای تمامی ستونها موجود است.
### مثالی از `schema` یک جدول:
```json
{
"first_name": {
"nullable": false
},
"last_name": {
"nullable": true
},
"country": {
"nullable": true,
"default": "Iran"
}
}
```
همچنین هر جدول خانهای به نام `data` دارد که شامل آرایهای از سطرهای موجود در جدول است.
### مثالی از یک جدول:
```json
{
"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"
}]
}
```
کل پروژه را در قالب کلاسی به نام `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`:
```php
$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
```
+ متد `select` را به گونهای پیادهسازی کنید که نام جدول و آرایهای از ستونها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستونهایشان با مقادیر ستونهای ورودی یکسان است را در قالب آرایه برگرداند. لزوماً مقادیر همهی ستونها بهعنوان ورودی به متد داده نمیشوند. فرض بر این است که بین شرطها جهت یافتن سطرهای خروجی `AND` وجود دارد. در صورتی که ستونی بهعنوان ورودی به متد داده نشود، باید همهی سطرها بهعنوان خروجی تابع `return` شود. در صورتی که ستونی به متد داده شود و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `select`:
```php
$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
```
+ متد `update` مقدار یک یا چند ستون را در سطرهایی که مشخص میکنیم تغییر میدهد. این متد شامل سه آرگومان بوده که آرگومان اول نام جدول موردنظر برای بهروزرسانی است، آرگومان دوم شامل مقادیر جدید ستونهایی است که قرار است تغییر کنند و آرگومان سوم مشخص میکند که ستونهای چه سطرهایی باید تغییر کنند؛ به طوری که هر سطری که مقادیر ستونهایش برابر با مقادیر ستونهای آرگومان سوم باشد باید بهروزرسانی شود. لزوماً مقادیر همهی ستونهای جدول در آرگومانهای دوم و سوّم موجود نیستند. اگر آرگومان سوّم به متد داده نشد، باید همهی سطرهای جدول بهروزرسانی شوند. فرض بر این است که بین شرطها جهت یافتن سطرها جهت بهروزرسانی `AND` وجود دارد. در صورتی که ستونی در آرگومان دوم یا سوّم موجود باشد و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `update`:
```php
$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
```
+ متد `delete` را به گونهای پیادهسازی کنید که نام جدول و آرایهای از ستونها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستونهایشان با مقادیر ستونهای ورودی یکسان است را از جدول حذف کند. لزوماً مقادیر همهی ستونها بهعنوان ورودی به متد داده نمیشوند. فرض بر این است که بین شرطها جهت یافتن سطرها برای حذف `AND` وجود دارد. در صورتی که ستونی بهعنوان ورودی به متد داده نشود، باید همهی سطرها از جدول حذف شوند. در صورتی که ستونی به متد داده شود و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `delete`:
```php
$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
```
**نکته:** در صورتی که جدول موردنظر در هر یک از متدهای `insert`، `select`، `update` یا `delete` یافت نشود، باید یک `Exception` با پیام `Table table_name not found` (`table_name` نام جدول موردنظر است) _throw_ شود.
## آنچه باید آپلود کنید
یک فایل _PHP_ که کلاس `JsonDB` درون آن قرار دارد آپلود کنید.
در این سؤال، شما باید یک بلاگ خیلی ساده را پیادهسازی کنید.
# جزئیات
پروژهی اولیه را از [این لینک](/contest/assignments/22512/download_problem_initial_project/73716/) دانلود کنید. ساختار فایلهای این پروژه بهصورت زیر است:
```
simple_blog_initial
├── app
│ ├── Console
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── Controller.php
│ │ │ └── PostController.php
│ │ ├── Middleware
│ │ │ ├── Authenticate.php
│ │ │ ├── EncryptCookies.php
│ │ │ ├── PreventRequestsDuringMaintenance.php
│ │ │ ├── RedirectIfAuthenticated.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── TrustHosts.php
│ │ │ ├── TrustProxies.php
│ │ │ └── VerifyCsrfToken.php
│ │ └── Kernel.php
│ ├── Models
│ │ ├── FavoritePost.php
│ │ ├── Post.php
│ │ └── User.php
│ ├── Override
│ │ ├── Connection.php
│ │ └── QueryBuilder.php
│ └── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── DatabaseServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── bootstrap
│ ├── cache
│ │ ├── packages.php
│ │ └── services.php
│ └── app.php
├── config
│ ├── app.php
│ ├── auth.php
│ ├── broadcasting.php
│ ├── cache.php
│ ├── cors.php
│ ├── database.php
│ ├── filesystems.php
│ ├── hashing.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php
│ ├── session.php
│ └── view.php
├── database
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 2014_10_12_000000_create_users_table.php
│ │ ├── 2014_10_12_100000_create_password_resets_table.php
│ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ ├── 2020_09_27_124807_create_table_posts.php
│ │ └── 2020_09_27_133130_create_table_favorite_posts.php
│ ├── seeders
│ │ └── DatabaseSeeder.php
│ └── database.sqlite
├── public
│ ├── favicon.ico
│ ├── index.php
│ ├── robots.txt
│ └── web.config
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ ├── lang
│ │ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
│ └── views
│ ├── posts
│ │ ├── create.blade.php
│ │ ├── edit.blade.php
│ │ ├── index.blade.php
│ │ └── show.blade.php
│ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ │ └── data
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ └── logs
├── tests
│ ├── Feature
│ │ ├── BlogSampleTest.php
│ │ ├── BlogTest.php
│ │ └── OrderByCustomTest.php
│ ├── Unit
│ ├── CreatesApplication.php
│ └── TestCase.php
├── README.md
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
<details class="brown">
<summary>راهاندازی پروژه</summary>
**برای اجرای پروژه، باید `php` و `composer` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
</details>
این بلاگ شامل صفحات زیر است:
1. **صفحهی نوشتن یک پست جدید (`/post/create`):** در این صفحه با وارد کردن یک تیتر و متن محتوای یک پست، آن پست ساخته میشود. پس از ساخت پست، کاربر به صفحهی نمایش آن پست هدایت میشود.
2. **صفحهی ویرایش یک پست (`/post/edit/{id}`):** در این صفحه، تیتر و محتوای فعلی پست نمایش داده میشود و قابل ویرایش است. پس از ویرایش، کاربر به صفحهی نمایش آن پست هدایت میشود.
3. **صفحهی نمایش یک پست (`/post/show/{id}`):** در این صفحه، تیتر و محتوای پست نمایش داده میشود.
4. **صفحه لیست تمام پستها (`/posts`):** در این صفحه، تمامی پستها نمایش داده میشوند و با کلیک بر روی تیتر هر پست، کاربر وارد صفحهی نمایش آن پست میشود.
هر پست شامل یک تیتر و یک متن محتوا است.
هر پست پس از نوشته شدن به لیست پستها در صفحه لیست تمام پستها اضافه میشود.
## مدلها
مدلها در پروژهی اولیه داده شده است. همچنین میتوانید با اجرای دستور `php artisan migrate` مایگریشنها را اجرا کنید و پایگاه داده را بهطور کامل داشته باشید. در این پروژه از پایگاه دادهی *SQLite* استفاده شده است. در ادامه، لیست مدلها و توضیحات آمده است:
1. `Post`: مدل پستها که دارای دو فیلد تیتر (`title`) و محتوا (`contents`) است
2. `FavoritePost`: مدل پستهای مورد علاقه که دارای یک فیلد `post_id` است و مشخصکنندهی پستهای موردعلاقه است. با کلیک روی لینک «اضافه به علاقهمندیها»، در صورتی که پست موردنظر از قبل موردعلاقه نباشد، یک ردیف به این جدول اضافه میشود.
شما باید `PostController` و *view*های مربوطه را طوری کامل کنید که عملیات ساخت، ویرایش و نمایش پستها (بهصورت تکی و در لیست) به درستی کار کند.
در صفحهی نمایش یک پست و همچنین صفحه نمایش لیست پستها، برای هر پست دکمهی «اضافه کردن به علاقهمندیها» وجود دارد. در صفحهی لیست پستها، ابتدا پستهایی که به لیست علاقهمندیها اضافه شدهاند بهترتیب زمان اضافه شدن به علاقهمندی نمایش داده میشوند و سپس بقیهی پستها بهترتیب تاریخ ایجاد.
## کنترلرها
متدهای زیر را در کنترلر `PostController` پیادهسازی کنید:
1. `index()`: این متد باید لیست پستها را با ترتیبی که قبلاً ذکر شد با نام `posts` به *view*`posts.index` ارسال کند.
2. `store(Request $request)`: این متد باید یک پست جدید با اطلاعات ورودی (`title` و `contents`) به پایگاه داده اضافه کرده و کاربر را به صفحهی `/post/show/{id}` هدایت کند.
3. `edit($id)`: این متد باید اطلاعات پست با شناسهی `$id` را با نام `post` به *view* `posts.edit` ارسال کند.
4. `update($id, Request $request)`: این متد باید پست با شناسهی `$id` را با اطلاعات ورودی (`title` و `contents`) بهروزرسانی کرده و کاربر را به صفحهی `/post/show/{id}` هدایت کند.
5. `show($id)`: این متد باید اطلاعات پست با شناسهی `$id` را با نام `post` به *view* `posts.show` ارسال کند.
6. `favorite($id)`: این متد باید پست با شناسهی `$id` را به جدول علاقهمندیها اضافه کرده و کاربر را به صفحهی قبلیاش هدایت کند.
برای نمایش پستهای اضافه شده به علاقهمندیها، شما باید در کلاس `\App\Providers\DatabaseServiceProvider` یک *macro* جدید با نام `orderByCustom` به `QueryBuilder` اضافه کنید. این *macro* باید به اینصورت کار کند:
```php
User::orderByCustom('id', 3, 7, 2, 8)->get();
```
کوئری معادل فراخوانی متد بالا:
```sql
select * from "users" order by (case "id" when 3 then 0 when 7 then 1 when 2 then 2 when 8 then 3 else 4 end)
```
همچنین اگر فقط یک آرگومان به این متد داده شود (یا آرگومانهای دوم به بعد خالی باشند)، نباید تغییری در کوئری اعمال شود.
**توجه:** شما تنها مجاز به تغییر فایلهای موجود در پوشهی `/app` هستید.
# آنچه باید آپلود کنید
پس از اعمال تغییرات، کل پروژه به غیر از پوشهی `vendor` را *Zip* کرده و آپلود کنید. نام فایل *Zip* اهمیتی ندارد.