برنامه نویسی

پشتیبانی بهتر خط لوله جمع آوری در درایور PHP MongoDB

این آموزش توسط آندریاس براون نوشته شده استبشر

چارچوب جمع آوری ابزاری قدرتمند در جعبه ابزار MongoDB شماست. اجازه می دهد
شما باید نمایش داده شدگان پیچیده را در مورد داده های خود اجرا کنید ، اسناد را متناسب با آن شکل دهید و اصلاح کنید
نیازهای شما این قدرت در بسیاری از مراحل مختلف خط لوله و
اپراتورها ، که به نوبه خود یک چالش یادگیری خاص را به وجود می آورد. قطب نما
شامل یک سازنده خط لوله جمع آوری است که به شما امکان می دهد نتایج را در زمان واقعی مشاهده کنید
برای هر مرحله و اشتباهات را زودتر برطرف کنید. پس از اتمام خط لوله شما ، شما
می توانید خط لوله را به زبان خود صادر کرده و از آن در کد خود استفاده کنید. در PHP
راننده ، آن خط لوله به عنوان یک آرایه ، کاملاً بدون تایپ زندگی می کند ، و
بعضی اوقات با ساختار نسبتاً پیچیده ای از مراحل و اپراتورها. بیایید
این خط لوله را از یکی از پروژه های من به عنوان نمونه بگیرید:

$pipeline = [
    [
        '$group' => [
            '_id' => [
                'year' => ['$year' => '$reportDate'],
                'month' => ['$month' => '$reportDate'],
                'fuelType' => '$fuelType',
                'brand' => '$station.brand'
            ],
            'lowest' => ['$min' => '$price'],
            'highest' => ['$max' => '$price'],
            'average' => ['$avg' => '$price'],
            'count' => ['$sum' => 1],
        ],
    ],
    [
        '$group' => [
            '_id' => [
                'year' => '$_id.year',
                'month' => '$_id.month',
                'brand' => '$_id.brand',
            ],
            'count' => ['$sum' => '$count'],
            'prices' => [
                '$push' => [
                    'k' => '$_id.fuelType',
                    'v' => [
                        'lowest' => '$lowest',
                        'highest' => '$highest',
                        'average' => '$average',
                        'span' => ['$subtract' => ['$highest', '$lowest']],
                    ],
                ],
            ],
        ],
    ],
    ['$addFields' => ['prices' => ['$arrayToObject' => '$prices']]],
    [
        '$group' => [
            '_id' => [
                'year' => '$_id.year',
                'month' => '$_id.month',
            ],
            'brands' => [
                '$push' => [
                    'brand' => '$_id.brand',
                    'count' => '$count',
                    'prices' => '$prices',
                ],
            ],
        ],
    ],
    [
        '$addFields' => [
            'brands' => [
                '$sortArray' => [
                    'input' => '$brands',
                    'sortBy' => ['count' => -1],
                ],
            ],
        ],
    ],
    ['$sort' => ['_id.year' => 1, '_id.month' => 1]],
];
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این منطق زیادی است! برای درک بهتر این خط لوله ، بیایید
به یک سند منبع واحد نگاه کنید:

{
  "reportDate": "2024-10-22T13:15:03+02:00",
  "station": {
    "brand": "Acme Corp."
  },
  "fuelType": "diesel",
  "price": "1.759"
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

من برخی از زمینه ها را که الان از آنها استفاده نمی کنیم ، کنار گذاشته ام. تجمع
خط لوله همه این اسناد را جمع می کند و یک سندی را برای هر ماه تولید می کند:

{
  "_id": {
    "year": 2024,
    "month": 10
  },
  "brands": [
    {
      "brand": "Acme Corp.",
      "count": 1,
      "prices": {
        "diesel": {
          "lowest": 1.759,
          "highest": 1.759,
          "average": 1.579,
          "span": 0
        }
      }
    }
  ]
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بدون اینکه به جزئیات بیشتری در این باره بپردازیم ، حتی اگر بخواهیم در مورد بخش هایی از آن اظهار نظر کنیم
خط لوله جمع آوری برای توضیح آنچه انجام می دهد ، هنوز هم زیاد خواهد بود
بار شناختی هنگام عبور از خط لوله جمع آوری. یک دلیل برای این
این تنها راه برای بیان دامنه چارچوب جمع آوری خاص است
زبان (DSL) از طریق آرایه های بدون نسخه است ، و هیچ ویرایشگر PHP نمی تواند چیزهای زیادی را ارائه دهد
به فراتر از برجسته سازی نحو کمک کنید. آن را با چند سطح لانه سازی جفت کنید ، و
شما خودتان نوع کدی را که می توانید بنویسید ، اما خوانده نشده اید. ما می توانیم
با اصلاح مجدد کد شروع کنید ، اما در عوض ، بیایید سعی کنیم از آرایه دور شویم
ساختارها و استفاده از یک راه حل بهتر.

معرفی سازنده خط لوله جمع آوری

پیش از این به عنوان یک بسته مستقل ، نسخه 1.21 از MongoDB PHP منتشر شده است
درایور هم اکنون شامل یک سازنده جامع تنظیم کننده جمع آوری است. به جای
نوشتن آرایه های پیچیده ، می توانید از روشهای کارخانه برای تولید مراحل خط لوله استفاده کنید
و اپراتورها در اینجا همان خط لوله ای است که قبلاً داشتیم ، این بار نوشته شده است
با سازنده خط لوله جمع آوری:

use MongoDB\Builder\Accumulator;
use MongoDB\Builder\Expression;
use MongoDB\Builder\Pipeline;
use MongoDB\Builder\Stage;
use function MongoDB\object;

$pipeline = new Pipeline(
    Stage::group(
        _id: object(
            year: Expression::year(Expression::dateFieldPath('reportDate')),
            month: Expression::month(Expression::dateFieldPath('reportDate')),
            fuelType: Expression::fieldPath('fuelType'),
            brand: Expression::fieldPath('station.brand'),
        ),
        lowest: Accumulator::min(Expression::doubleFieldPath('price')),
        highest: Accumulator::max(Expression::doubleFieldPath('price')),
        average: Accumulator::avg(Expression::doubleFieldPath('price')),
        count: Accumulator::sum(1),
    ),
    Stage::group(
        _id: object(
            year: Expression::fieldPath('_id.year'),
            month: Expression::fieldPath('_id.month'),
            brand: Expression::fieldPath('_id.brand'),
        ),
        count: Accumulator::sum(Expression::intFieldPath('count')),
        prices: Accumulator::push(
            object(
                k: Expression::fieldPath('_id.fuelType'),
                v: object(
                    lowest: Expression::fieldPath('lowest'),
                    highest: Expression::fieldPath('highest'),
                    average: Expression::fieldPath('average'),
                    range: Expression::subtract(
                        Expression::doubleFieldPath('highest'),
                        Expression::doubleFieldPath('lowest'),
                    ),
                )
            )
        )
    ),
    Stage::addFields(
        prices: Expression::arrayToObject(
            Expression::arrayFieldPath('prices'),
        ),
    ),
    Stage::group(
        _id: object(
            year: Expression::fieldPath('_id.year'),
            month: Expression::fieldPath('_id.month'),
        ),
        brands: Accumulator::push(
            object(
                brand: Expression::fieldPath('_id.brand'),
                count: Expression::fieldPath('count'),
                prices: Expression::fieldPath('prices'),
            ),
        ),
    ),
    Stage::addFields(
        brands: Expression::sortArray(
            input: Expression::arrayFieldPath('brands'),
            sortBy: ['count' => -1],
        ),
    ),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این هنوز یک خط لوله پیچیده است ، اما در مقایسه با مثال اصلی آرایه ،
اکنون استنباط زمینه هر یک از مؤلفه های خط لوله بسیار ساده تر است. عملگر
به وضوح از نام فیلد متمایز می شوند ، و این تایپ می تواند کد را فعال کند
ویراستاران و ابزار برای کمک بهتر به توسعه دهنده.

برای اجرای یک خط لوله جمع آوری ، می توانید از Pipeline نمونه ای از هر روش
که می تواند یک خط لوله تجمیع را دریافت کند ، مانند Collection::aggregate یا
Collection::watchبشر علاوه بر این ، روش هایی مانند Collection::updateMany وت
Collection::findOneAndUpdate می تواند دریافت کند Pipeline نمونه برای اجرای
به روز رسانی به خاطر داشته باشید که شما قادر به استفاده از همه جمع آوری موجود نخواهید بود
مراحل خط لوله در عملیات به روزرسانی. از آنجا که می توان از یک نمونه خط لوله استفاده کرد
در همه جا ، سازنده به شما امکان می دهد مراحل را به خط لوله ای اضافه کنید که نیست
در به روزرسانی ها پشتیبانی می شوند.

طراحی سازنده

سازنده با سهولت استفاده در ذهن طراحی شده است. از همه مهمتر ، ما می خواستیم
برای نشان دادن سیستم نوع تا حدودی انعطاف پذیر و راهنمایی های بهتری به کاربران
هنگام نوشتن خطوط لوله جمع آوری. به همین دلیل عباراتی مانند خواهید دید
dateFieldPathبا doubleFieldPath، یا arrayFieldPathبشر هر عبارت
هنگام ارزیابی ، به یک نوع خاص برطرف می شود. به عنوان مثال ، ما می دانیم که
$year بیان اپراتور به یک عدد صحیح حل می شود ، و استدلال آن یک است
بیان که به یک تاریخ ، زمان بندی یا ObjectID برطرف می شود. در حالی که ما می توانیم استفاده کنیم
$reportDate به مرجع reportDate زمینه سندی که مورد ارزیابی قرار می گیرد ،
dateFieldPath بیانگر تر است و قصد دریافت یک قسمت تاریخ را نشان می دهد.
این همچنین به IDE هایی مانند PhpStorm اجازه می دهد تا هنگام ارائه پیشنهادات بهتری ارائه دهند
تکمیل کد و همچنین مقدار مشخصی از بررسی نوع برای اطمینان از شما
ایجاد خطوط لوله که منجر به خطاهای سرور می شود.

برای همه عبارات ، کلاسهای کارخانه ای با روش هایی برای ایجاد وجود دارد
اشیاء بیان استفاده از روشهای استاتیک باعث می شود کد کمی بیشتر شود
لفظی ، اما استفاده از توابع به دلیل درگیری بین تجمع غیرممکن بود
نام اپراتور و کلمات کلیدی رزرو شده در PHP (به عنوان مثال ، andبا ifبا switch). من
بعداً در این پست گزینه های دیگری را برای استفاده از این روشهای استاتیک نشان دهید.

ویژگی جایزه: اشیاء پرس و جو

به عنوان یک عارضه جانبی در ساخت سازنده خط لوله جمع آوری ، اکنون نیز وجود دارد
سازنده فیلترهای پرس و جو. این به این دلیل است که $match مرحله یک پرس و جو می کند
شیء ، و برای جلوگیری از بازگشت به آرایه های پرس و جو مانند شما که آنها را به آنها منتقل می کنند
Collection::find، ما همچنین یک سازنده برای اشیاء پرس و جو ساختیم. در اینجا ، شما می بینید
مثال a find با همان پرس و جو مشخص شده با استفاده از سازنده تماس بگیرید:

$collection->find(['score' => ['$gt' => 70, '$lt' => 90]]);

$collection->find(
    Query::query(
        score: [
            Query::gt(70),
            Query::lt(90),
        ],
    ),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در حالی که این کمی لفظ تر است ، اما یک API بیانگر تر از PHP را فراهم می کند
ساختار آرایه و پیشرفت های یکسانی را برای IDE ها و ابزار به ارمغان می آورد. این است
به شما تصمیم می گیرید که کدام گزینه را بهتر دوست دارید.

اصلاح مجدد برای حفظ بهتر

زمینه ها را به متغیرها استخراج کنید

با توضیح جزئیات سازنده اصلی ، هنوز یک مشکل وجود دارد: سازنده
به شما در نوشتن خط لوله کمک می کند ، اما واقعاً خطوط لوله موجود را بیشتر نمی کند
قابل حفظ بله ، خواندن آنها را آسان تر می کند ، اما یک خط لوله پیچیده
به همان اندازه پیچیده باقی مانده است. بنابراین ، بیایید در مورد برخی از بازپرداخت هایی که می توانیم برای هر دو مورد بحث کنیم
خوانایی خط لوله را بهبود بخشید و قسمت هایی از خط لوله را قابل استفاده مجدد کنید.
توجه داشته باشید که اگرچه مثال زیر از سازنده تجمع استفاده می کند ، همان
پیشنهادات همچنین می تواند در خطوط لوله ای که به عنوان آرایه های PHP نوشته شده است ، اعمال شود.

بیایید به اولین نگاه کنیم $group مرحله در مثال اصلی:

Stage::group(
    _id: object(
        year: Expression::year(Expression::dateFieldPath('reportDate')),
        month: Expression::month(Expression::dateFieldPath('reportDate')),
        fuelType: Expression::fieldPath('fuelType'),
        brand: Expression::fieldPath('station.brand'),
    ),
    lowest: Accumulator::min(Expression::doubleFieldPath('price')),
    highest: Accumulator::max(Expression::doubleFieldPath('price')),
    average: Accumulator::avg(Expression::doubleFieldPath('price')),
    count: Accumulator::sum(1),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

همانطور که مشاهده می کنید ، ما به reportDate وت price زمینه ها چندین بار.
یک بازپرداخت سریع استخراج آن ها به متغیرها است:

$reportDate = Expression::dateFieldPath('reportDate');
$price = Expression::doubleFieldPath('price');

Stage::group(
    _id: object(
        year: Expression::year($reportDate),
        month: Expression::month($reportDate),
        fuelType: Expression::fieldPath('fuelType'),
        brand: Expression::fieldPath('station.brand'),
    ),
    lowest: Accumulator::min($price),
    highest: Accumulator::max($price),
    average: Accumulator::avg($price),
    count: Accumulator::sum(1),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در fuelType وت station.brand زمینه ها نیز می توانند استخراج شوند. من انتخاب نکردم
از آنجا که آنها فقط یک بار مورد استفاده قرار می گیرند ، اما ممکن است بخواهید این کار را به نفع قوام انجام دهید.

نظرات یا روشها

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

public static function groupAndComputeStatistics(
    stdClass $groupBy,
    Expression\ResolvesToDouble $price,
): GroupStage {
    return Stage::group(
        _id: $groupBy,
        lowest: Accumulator::min($price),
        highest: Accumulator::max($price),
        average: Accumulator::avg($price),
        count: Accumulator::sum(1),
    );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

هنگام استخراج منطق ، در نظر بگیرید که آیا این روش در جای دیگر قابل استفاده مجدد است یا خیر. در این
مورد ، با نگه داشتن _id زمینه برای $group همراه با قیمت
زمینه به عنوان یک پارامتر ، می توانیم از این روش سازنده در یک خط لوله مختلف استفاده مجدد کنیم.

$reportDate = Expression::dateFieldPath('reportDate');
$price = Expression::doubleFieldPath('price');

self::groupAndComputeStatistics(
    groupBy: object(
        year: Expression::year($reportDate),
        month: Expression::month($reportDate),
        fuelType: Expression::fieldPath('fuelType'),
        brand: Expression::fieldPath('station.brand'),
    ),
    price: $price,
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

چندین مرحله را استخراج کنید

ما اکنون مرحله اول خط لوله را دوباره اصلاح کرده ایم ، اما $group مرحله زیر است
همچنین نسبتاً پیچیده برای خواندن. برای بدتر شدن اوضاع ، این مرحله خط لوله
همراه با $addFields مرحله زیر: $group لیستی از
انواع سوخت با قیمت آنها ، که سپس به یک شی در تبدیل می شود
$addFieldsبشر در حالت ایده آل ، ما می خواهیم این جزئیات اجرای را پنهان کنیم و استخراج کنیم
هر دو مرحله با هم.

برای انجام این کار ، ما یک بار دیگر یک روش کارخانه را استخراج می کنیم ، به جز اینکه این بار ، ما
بازگشت a Pipeline مثال:

public static function groupAndAssembleFuelTypePriceObject(
    stdClass $groupBy,
    Expression\ResolvesToString $fuelType,
    Expression\ResolvesToInt $count,
    Expression\ResolvesToDouble $lowest,
    Expression\ResolvesToDouble $highest,
    Expression\ResolvesToDouble $average,
): Pipeline {
    return new Pipeline(
        Stage::group(
            _id: $groupBy,
            count: Accumulator::sum($count),
            prices: Accumulator::push(
                object(
                    k: $fuelType,
                    v: object(
                        lowest: $lowest,
                        highest: $highest,
                        average: $average,
                        range: Expression::subtract(
                            $highest,
                            $lowest,
                        ),
                    )
                )
            )
        ),
        Stage::addFields(
            prices: Expression::arrayToObject(
                Expression::arrayFieldPath('prices'),
            ),
        ),
    );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

self::groupAndAssembleFuelTypePriceObject(
    groupBy: object(
        year: Expression::fieldPath('_id.year'),
        month: Expression::fieldPath('_id.month'),
        brand: Expression::fieldPath('_id.brand'),
    ),
    fuelType: Expression::fieldPath('_id.fuelType'),
    count: Expression::intFieldPath('count'),
    lowest: Expression::fieldPath('lowest'),
    highest: Expression::fieldPath('highest'),
    average: Expression::fieldPath('average'),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بدون اینکه به جزئیات بیش از حد بپردازیم ، می توانیم با مراحل بعدی همین کار را انجام دهیم
خط لوله به استثنای روشهای کارخانه استخراج شده ، خط لوله ما اکنون به نظر می رسد
مثل این:

$reportDate = Expression::dateFieldPath('reportDate');
$price = Expression::doubleFieldPath('price');

$pipeline = new Pipeline(
    self::groupAndComputeStatistics(
        groupBy: object(
            year: Expression::year($reportDate),
            month: Expression::month($reportDate),
            fuelType: Expression::fieldPath('fuelType'),
            brand: Expression::fieldPath('station.brand'),
        ),
        price: $price,
    ),
    self::groupAndAssembleFuelTypePriceObject(
        groupBy: object(
            year: Expression::fieldPath('_id.year'),
            month: Expression::fieldPath('_id.month'),
            brand: Expression::fieldPath('_id.brand'),
        ),
        fuelType: Expression::fieldPath('_id.fuelType'),
        count: Expression::intFieldPath('count'),
        lowest: Expression::fieldPath('lowest'),
        highest: Expression::fieldPath('highest'),
        average: Expression::fieldPath('average'),
    ),
    self::groupBrandsAndSort(
        groupBy: object(
            year: Expression::fieldPath('_id.year'),
            month: Expression::fieldPath('_id.month'),
        ),
        brand: Expression::fieldPath('_id.brand'),
        count: Expression::fieldPath('count'),
        prices: Expression::fieldPath('prices'),
    ),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

عبارات پیچیده را استخراج کنید

تا کنون ، ما فقط مراحل کل خط لوله را که حاوی نسبتاً است استخراج کرده ایم
عبارات ساده گاهی اوقات ، خط لوله جمع آوری شما حاوی موارد بیشتری خواهد بود
بیان پیچیده از همان پروژه ای که مثال قبلی را به دست آورد ،
این گوهر نیز وجود دارد که بخشی از خط لوله است که وزنه برداری را محاسبه می کند
قیمت متوسط ​​برای هر روز:

$pipeline = [
    [
        '$addFields' => [
            'weightedPrices' => [
                '$map' => [
                    'input' => '$prices',
                    'as' => 'priceReport',
                    'in' => [
                        'duration' => [
                            '$min' => [
                                [
                                    '$dateDiff' => [
                                        'startDate' => '$$priceReport.previous.reportDate',
                                        'endDate' => '$$priceReport.reportDate',
                                        'unit' => 'second',
                                    ],
                                ],
                                [
                                    '$add' => [
                                        [
                                            '$multiply' => [
                                                ['$hour' => '$$priceReport.reportDate'],
                                                3600,
                                            ],
                                        ],
                                        [
                                            '$multiply' => [
                                                ['$minute' => '$$priceReport.reportDate'],
                                                60,
                                            ],
                                        ],
                                        ['$second' => '$$priceReport.reportDate'],
                                    ],
                                ],
                            ],
                        ],
                        'price' => '$$priceReport.previous.price',
                    ],
                ],
            ],
            'lastPrice' => ['$last' => '$prices'],
        ],
    ],
];
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سازنده می تواند این موضوع را کمی مختصر تر کند ، اما پیچیدگی باقی مانده است:

$prices = Expression::arrayFieldPath('prices');
$reportDate = Expression::variable('priceReport.reportDate');
$previousReportDate = Expression::variable('priceReport.previous.reportDate');

$pipeline = new Pipeline(
    Stage::addFields(
        weightedPrices: Expression::map(
            input: $prices,
            in: object(
                duration: Expression::min(
                    Expression::dateDiff(
                        startDate: $previousReportDate,
                        endDate: $reportDate,
                        unit: 'second',
                    ),
                    Expression::add(
                        Expression::multiply(
                            Expression::hour($reportDate),
                            3600,
                        ),
                        Expression::multiply(
                            Expression::minute($reportDate),
                            60,
                        ),
                        Expression::second($reportDate),
                    )
                ),
                price: Expression::variable('priceReport.previous.price'),
            ),
            as: 'priceReport',
        ),
        lastPrice: Expression::last($prices),
    ),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

public static function computeElapsedSecondsOnDay(
    Expression\ResolvesToDate $date,
): Expression\ResolvesToInt {
    return Expression::add(
        Expression::multiply(
            Expression::hour($date),
            3600,
        ),
        Expression::multiply(
            Expression::minute($date),
            60,
        ),
        Expression::second($date),
    );
}

/**
 * Returns the time that has elapsed between two dates, in seconds.
 *
 * If the dates are on the same day, the difference is computed between the
 * two times. If the dates fall on different dates, the duration returned is
 * the number of seconds since the start of the day from the second date
 * argument.
 */
public static function computeDurationBetweenDates(
    Expression\ResolvesToDate $startDate,
    Expression\ResolvesToDate $endDate,
): Expression\ResolvesToInt {
    return Expression::min(
        Expression::dateDiff(
            startDate: $startDate,
            endDate: $endDate,
            unit: 'second',
        ),
        self::computeElapsedSecondsOnDay($endDate),
    );
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این باعث می شود پیچیدگی مرحله خط لوله به طرز چشمگیری کاهش یابد:

$prices = Expression::arrayFieldPath('prices');
$reportDate = Expression::variable('priceReport.reportDate');
$previousReportDate = Expression::variable('priceReport.previous.reportDate');

$pipeline = new Pipeline(
    Stage::addFields(
        weightedPrices: Expression::map(
            input: $prices,
            in: object(
                duration: self::computeDurationBetweenDates($previousReportDate, $reportDate),
                price: Expression::variable('priceReport.previous.price'),
            ),
            as: 'priceReport',
        ),
        lastPrice: Expression::last($prices),
    ),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سازنده خود را ایجاد کنید

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

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

فقط از انتزاع زودرس مراقب باشید: منطق پیچیده را با هدف استخراج کنید
خواندن و آزمایش کردن کد خود را تر کنید ، تا بتوانید از آن استفاده مجدد کنید
کل پایه کد شما

داخلی

در مثالهایی که تاکنون دیده اید ، ما فقط به مراحل نگاه کرده ایم ،
عبارات و البته Pipeline اشیاء اما چگونه می توانیم با یک
خط لوله جمع آوری که سرور می فهمد؟ اشیاء ایجاد شده با
روشهای مختلف کارخانه اشیاء نگهدارنده ارزش هستند. آنها هیچ منطقی ندارند ،
اما آنها به ما این امکان را می دهند که بدانیم یک عبارت به چه نوع برطرف می شود.

برای مثال ، بیایید $hour اپراتور هنگام استفاده
Expression::hour، شما نمونه ای از HourOperator کلاس.
این کلاس پیاده سازی می کند OperatorInterface، به سازنده بگویید که این است
اپراتوری که می تواند در یک مرحله خط لوله تجمع استفاده شود. همچنین
پیاده سازی ResolvesToInt رابط ، همانطور که همیشه می دانیم ارزیابی
بیان منجر به مقدار عدد صحیح می شود. مورد نیاز date پارامتر
اپراتور تاریخ است که در سازنده یکی از موارد بسیاری است. می تواند یک باشد
MongoDB\BSON\UTCDateTime به عنوان مثال ، اما این می تواند نتیجه هر یک باشد
اپراتوری که تاریخ را برمی گرداند ، به عنوان مثال $dateFromStringبشر

اکنون که ما در مورد این اشیاء دارنده ارزش می دانیم ، هنوز هم باید اطمینان حاصل کنیم
سرور می داند که ما در مورد چه چیزی صحبت می کنیم. وقتی تماس می گیرید Collection::aggregate
با خط لوله ای که ساخته اید ، چه اتفاقی در آن می افتد؟ در اینجا ، یک سری از
رمزگذارها بهار عمل می شوند. ما از یک نقطه ورودی استفاده می کنیم ، BuilderEncoder
کلاس. این کلاس شامل چندین رمزگذار است که قادر به کنترل همه هستند
مراحل خط لوله ، اپراتورها و باتری ها و آنها را به BSON خود تبدیل می کنند
بازنمودها

این به ما امکان می دهد منطق را قابل تنظیم نگه داریم. به عنوان مثال ، دکترین mongoDB
ODM به کاربران اجازه می دهد تا نام های مختلفی را برای زمینه های موجود در پایگاه داده مشخص کنند. به نوبه خود ،
باید نام یک ملک را در کلاس نقشه برداری به نام تبدیل کند
قسمت در پایگاه داده. چنین ویژگی ای را می توان با ایجاد یک عرف ساخته شد
رمزگذار برای همه fieldPath عبارات و تغییر مسیر میدانی
بر این اساس

هنگام ایجاد MongoDB\Client به عنوان مثال ، اکنون می توانید یک چیز اضافی را منتقل کنید
builderEncoder گزینه در $driverOptions بحث این را مشخص می کند
رمزگذار برای رمزگذاری خطوط لوله تجمع ، بلکه از اشیاء پرس و جو استفاده می شود. همه
Database وت Collection نمونه ها این مقدار را از مشتری به ارث می برند ، اما شما
می توانید هنگام انتخاب آن اشیاء ، آن را از طریق گزینه ها غلبه کنید. این اجازه می دهد
شما باید منطق سفارشی خود را در هر زمان که خطوط لوله یا نمایش داده شود ، اعمال شود
برای سرور

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

ما می توانیم سعی کنیم به هوش مصنوعی مولد اعتماد کنیم تا در این زمینه به ما کمک کنیم ، اما این فقط پیش می رود
تا کنون در عوض ، ما از تولید کد استفاده می کنیم تا کار را آسانتر کنیم. همه
کارخانه ها ، دارندگان ارزش و رمزگذارها از یک پیکربندی تولید می شوند. کی
یک اپراتور جدید معرفی شده است ، ما یک فایل پیکربندی با تمام جزئیات آن ایجاد می کنیم:
انواع ورودی ، آنچه اپراتور برطرف می کند ، مستندات پارامترها ، و
حتی نمونه هایی از مستندات MongoDB. سپس ما اجرا می کنیم
ژنراتور و تمام کد لازم برای استفاده از اپراتور به آنها داده می شود.

به نظر می رسد که این به اندازه کافی خوب نبود ، ژنراتور نمونه هایی را که اضافه کردیم نیز می گیرد
قبل و آنها را به آزمایشات اضافه می کند. در این تست ها ، ما به صورت دستی سازنده را می نویسیم
کدی که خط لوله یا بیان را از مثال تولید می کند. این
تضمین می کند که منطق تولید شده همانطور که انتظار داریم رفتار کند ، از آن محافظت می شود
رگرسیون باید تغییراتی در ژنراتور ایجاد کنیم و این امکان را به ما می دهد تا از آن استفاده کنیم
سازنده و احساس می کند که چگونه است. در نهایت ، ما دوست داریم این سازنده را اضافه کنیم
کد به مستندات سرور ، مشابه نحوه اضافه کردن زبان دیگر خاص
قطعه کد ، اما ما هنوز کاملاً در آنجا نیستیم.

Syntax Builder: روش ها یا توابع استاتیک؟

من قبلاً ذکر کردم که سازنده به دلیل نامگذاری از روشهای استاتیک استفاده می کند
درگیری این واقعاً به چند نام می رسد – برای مثال ، $match مرحله
وت $and یا $switch اپراتورها در حالی که PHP به ما اجازه می دهد تا از کلمات کلیدی رزرو شده استفاده کنیم
به عنوان نام روش های یک کلاس ، آنها برای نام عملکرد مجاز نیستند. ما
استفاده از پسوندها را برای توابع نامگذاری در نظر گرفته اند:

function andQuery(...) {}
function matchStage(...) {}
function switchOperator(...) {}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اضافه کردن پسوند به هر مرحله و اپراتور واقعاً باعث کاهش لفظی نمی شود ، بنابراین
ما در برابر آن تصمیم گرفتیم. گزینه دیگر استفاده از پیشوند زیرک برای همه بود
نامهای رزرو شده:

function _and(...) {}
function _match(...) {}
function _switch(...) {}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ما همچنین در برابر این گزینه تصمیم گرفتیم ، تا حدودی به این دلیل که علائم زیربنایی (یا در
حداقل برای علامت گذاری) توابع یا روشهای خصوصی ، بلکه به این دلیل است که
زیرکانه مرتب سازی الفبایی را در تکمیل کد می شکند.

یک رویکرد کمتر از جدی استفاده از ایموجی ها بود که من یاد گرفتم که در آن معتبر بود
PHP:

function 🔍and(...) {}
function 💲match(...) {}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در صورت عدم تمایل به روشهای استاتیک ، با جدیت ، گزینه های دیگری وجود دارد.
PHP's
نحو قابل تماس با کلاس اول
به شما امکان می دهد از یک روش موجود بسته شود و آن را در یک ذخیره کنید
متغیر برای استناد به بسته شدن:

$reportDate = Expression::dateFieldPath('reportDate');
$price = Expression::doubleFieldPath('price');
$fuelType = Expression::fieldPath('fuelType');
$brand = Expression::fieldPath('station.brand');

$group = Stage::group(...);
$year = Expression::year(...);
$month = Expression::month(...);
$min = Accumulator::min(...);
$max = Accumulator::max(...);
$avg = Accumulator::avg(...);
$sum = Accumulator::sum(...);

$group(
    _id: object(
        year: $year($reportDate),
        month: $month($reportDate),
        fuelType: $fuelType,
        brand: $brand,
    ),
    lowest: $min($price),
    highest: $max($price),
    average: $avg($price),
    count: $sum(1),
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

باز هم ، استفاده از PHP از $ برای علامت گذاری متغیرها با خط لوله جمع آوری خوب است
استفاده از آن برای مراحل و اپراتورها. ما در نظر گرفته ایم که یک روش را به هر یک اضافه کنیم
کلاس کارخانه ای که باعث می شود مجموعه ای از بسته شدن ها برای آسانتر این کار را بازگرداند:

['year' => $year, 'month' => $month] = Accumulator::accumulators();

// Alternatively, if you want to use all accumulators as variables:
extract(Accumulator::accumulators());
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ما در حال حاضر این کار را اجرا نکرده ایم ، اما اگر این نحو را به استاتیک ترجیح می دهید
روشها ، یا اگر پیشنهادات جایگزین دارید ، لطفاً در مورد آنها به ما اطلاع دهید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا