Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Building Web Apps with Laravel  Module 11 – Inside the framework

Collections, strings, numbers and dates

Turn rows into reports with Laravel collections, format strings and numbers for Indian readers, and keep dates right across time zones with Carbon.

  • Beginner
  • 10 minutes
  • Examples run with PHP 8.5.11, Composer 2.10.3, Laravel 13.34.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 4 of 10 minutes free
Free: Collections turn rows into a report, one method at a time · Str and Number format strings and numbers for readers · Carbon stores UTC and shows the reader's time. With a pass: 2 in-depth parts and 3 interview answers, about 6 minutes.

What you will learn

  • Transform rows into a report with collection methods such as filter, map, groupBy, keyBy and reduce
  • Use lazy collections and cursors to process large inputs in little memory
  • Format strings, numbers and currency for Indian readers with Str and Number
  • Handle dates and time zones with Carbon, storing UTC and showing India's time

Before you start

What you’ll learn · Before you start

What you will learn

  • Transform rows into a report with collection methods such as filter, map, groupBy, keyBy and reduce
  • Use lazy collections and cursors to process large inputs in little memory
  • Format strings, numbers and currency for Indian readers with Str and Number
  • Handle dates and time zones with Carbon, storing UTC and showing India's time

Before you start

On this page

Pathshala’s accountant wants April’s fees per batch from 12 instalment rows, each amount in Indian digit grouping such as 1,23,456.78, and each payment time shown in India although the server stored it in UTC. Laravel ships a tool for each part of that job: collections for lists, Str for strings, Number for numbers and Carbon for dates.

This lesson builds the fee report with collections, formats its strings and numbers for Indian readers, and shows its dates in India’s time. Later parts stream inputs too large for memory, write the short Indian forms that Number lacks, and test the day boundaries that a UTC timestamp crosses.

Collections turn rows into a report, one method at a time

A collection: A Laravel object that wraps an array and offers chainable methods such as filter, map, groupBy and sum, each returning a new collection. collect() makes one, and Eloquent queries return them. wraps an array in an object whose chaining methods each return a new collection, so a report reads top to bottom as the steps it takes. collect($rows) makes one, and Eloquent queries return them too. The run builds Pathshala’s report from 12 rows:

The pipeline, and the twelve rows it reads

report/report_probe.php

<?php

// Turns Pathshala's instalment rows into a fee report with one collection pipeline, printing what each step gives.
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

$rupees = fn (int $paise) => number_format($paise / 100, 2);
$rows = collect(require __DIR__.'/instalments.php');

echo 'rows: '.$rows->count().PHP_EOL;

// 1. Keep the rows that were paid, then add a month key to each.
$paid = $rows->filter(fn (array $row) => $row['status'] === 'paid')
    ->map(fn (array $row) => $row + ['month' => substr($row['due_on'], 0, 7)]);
echo 'paid rows: '.$paid->count().PHP_EOL;

// 2. Group by batch, then by month, and total each group.
$report = $paid->groupBy(['batch', 'month'])
    ->map(fn ($months) => $months->map(fn ($group) => $group->sum('amount_paise')));

echo PHP_EOL.'collected, by batch and month (rupees):'.PHP_EOL;
foreach ($report as $batch => $months) {
    foreach ($months as $month => $paise) {
        echo '  '.str_pad($batch, 16).$month.'  '.str_pad($rupees($paise), 9, ' ', STR_PAD_LEFT).PHP_EOL;
    }
}

// 3. One total per batch, with reduce.
$byBatch = $paid->groupBy('batch')->map(fn ($group) => $group->reduce(fn (int $sum, array $row) => $sum + $row['amount_paise'], 0));
echo PHP_EOL.'collected per batch: '.$byBatch->map($rupees)->toJson().PHP_EOL;

// 4. Who owes money: overdue rows keyed by student, with their totals.
$overdue = $rows->where('status', 'overdue')->groupBy('student')->map->sum('amount_paise');
echo 'overdue per student: '.$overdue->map($rupees)->toJson().PHP_EOL;

// 5. keyBy keeps the LAST row for each key.
echo 'keyBy(student) on 12 rows keeps '.$rows->keyBy('student')->count().' rows, Asha\'s due_on: '.$rows->keyBy('student')['Asha Verma']['due_on'].PHP_EOL;

report/instalments.php

<?php

// Twelve fee instalments of Pathshala's two batches for April to June 2031: invented students, amounts in paise.
return [
    ['batch' => 'Maths Morning', 'student' => 'Asha Verma',   'due_on' => '2031-04-05', 'amount_paise' => 118000, 'status' => 'paid'],
    ['batch' => 'Maths Morning', 'student' => 'Kabir Singh',  'due_on' => '2031-04-05', 'amount_paise' => 118000, 'status' => 'paid'],
    ['batch' => 'Physics Evening', 'student' => 'Meera Iyer', 'due_on' => '2031-04-10', 'amount_paise' => 95000,  'status' => 'paid'],
    ['batch' => 'Physics Evening', 'student' => 'Rohan Das',  'due_on' => '2031-04-10', 'amount_paise' => 95000,  'status' => 'overdue'],
    ['batch' => 'Maths Morning', 'student' => 'Asha Verma',   'due_on' => '2031-05-05', 'amount_paise' => 118000, 'status' => 'paid'],
    ['batch' => 'Maths Morning', 'student' => 'Kabir Singh',  'due_on' => '2031-05-05', 'amount_paise' => 118000, 'status' => 'overdue'],
    ['batch' => 'Physics Evening', 'student' => 'Meera Iyer', 'due_on' => '2031-05-10', 'amount_paise' => 95000,  'status' => 'paid'],
    ['batch' => 'Physics Evening', 'student' => 'Rohan Das',  'due_on' => '2031-05-10', 'amount_paise' => 95000,  'status' => 'overdue'],
    ['batch' => 'Maths Morning', 'student' => 'Asha Verma',   'due_on' => '2031-06-05', 'amount_paise' => 118000, 'status' => 'pending'],
    ['batch' => 'Maths Morning', 'student' => 'Kabir Singh',  'due_on' => '2031-06-05', 'amount_paise' => 118000, 'status' => 'pending'],
    ['batch' => 'Physics Evening', 'student' => 'Meera Iyer', 'due_on' => '2031-06-10', 'amount_paise' => 95000,  'status' => 'pending'],
    ['batch' => 'Physics Evening', 'student' => 'Rohan Das',  'due_on' => '2031-06-10', 'amount_paise' => 95000,  'status' => 'pending'],
];
A fee report from twelve instalment rows Python · report_session.py
"""A fee report from twelve instalment rows with one collection pipeline: filter, map, groupBy, sum, reduce and keyBy,
each step printed. Files: the folder report/ next to this file (instalments.php holds the rows)."""

from session import run

run(
    """
    php report_probe.php
    """,
    app=True,
    overlay=("report",),
)

Output

$ php report_probe.php
rows: 12
paid rows: 5

collected, by batch and month (rupees):
  Maths Morning   2031-04   2,360.00
  Maths Morning   2031-05   1,180.00
  Physics Evening 2031-04     950.00
  Physics Evening 2031-05     950.00

collected per batch: {"Maths Morning":"3,540.00","Physics Evening":"1,900.00"}
overdue per student: {"Rohan Das":"1,900.00","Kabir Singh":"1,180.00"}
keyBy(student) on 12 rows keeps 4 rows, Asha's due_on: 2031-06-05

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 report_session.py

Step through what each method left in the collection:

Interactive · stage by stage From twelve rows to four totals Step through the pipeline of the run. Each stage says what the collection holds after that method.
Twelve instalment rows become a fee report, step 5 of 5. Showing collect($rows), filter(paid), map(add month), groupBy(batch, month), map(map(sum)). In focus: map(map(sum)).collect($rows)12 rowsfilter(paid)5 rowsmap(add month)5 rows with a monthgroupBy(batch, month)2 batches, 4 groupsmap(map(sum))4 totals in paise

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Twelve rows in a collection. collect() wraps the array of 12 instalments of two batches, April to June, so every later step is a method call that returns a new collection.
  2. Keep the paid rows. filter() runs its closure on each row and keeps the rows for which it returns true. Five of the twelve were paid; the rest are overdue or pending.
  3. Add the month to each row. map() returns a new row for each old one. Here it adds a month key, 2031-04 or 2031-05, cut from the due date, and the original rows stay as they were.
  4. Group by batch, then month. groupBy() with two keys nests the groups, first by batch and then by month. Maths Morning has April and May, and Physics Evening has the same two months.
  5. Total each group. The outer map() visits each batch and the inner map() sums each month's group. The run printed 2,360.00 for Maths Morning in April, the two paid rows of 1,180.00.

Five of the twelve rows were paid. groupBy(['batch', 'month']) nested the groups by batch and month, and the inner sum gave 2,360.00 rupees for Maths Morning in April. reduce() folds a group into one value with a closure of your own and gave the same per-batch totals. Writing map->sum(...) is a higher-order message: A short form for a collection method that calls the same method on each item: map->sum('amount_paise') means map with a closure that calls sum('amount_paise') on every item., short for a closure that calls sum on each item.

Building a lookup with keyBy when a key repeats

keyBy('student') turned 12 rows into 4 and kept each student’s last row, Asha’s June instalment. Use groupBy() when a key can repeat, and keep keyBy() for keys that are unique.

For an input too large for memory, lazy collection: A collection that produces its items one at a time from a generator instead of holding an array, so its methods can work through an input larger than memory. offer the same methods but read one item at a time.

Checkpoint

Check: A collection pipeline for a report

  1. Question 1 of 2 In the run, Maths Morning had two paid instalments of 1,180.00 rupees each in April. What total, in rupees, did groupBy(['batch', 'month']) followed by the inner sum print for that group?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 2360 rupees

    The filter kept the paid rows, groupBy put Asha's and Kabir's April rows in one group, and sum('amount_paise') added 118000 + 118000 = 236000 paise, which the report printed as 2,360.00 rupees.

  2. Question 2 of 2 keyBy('student') ran on the 12 rows, in which each student has 3 instalments. What did it return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 4 rows, one per student, each the student's last instalment

    keyBy builds a lookup by the key and silently overwrites earlier rows with the same key. When a key repeats, use groupBy, which keeps every row.

Str and Number format strings and numbers for readers

Str holds Laravel’s helpers for strings, and Str::of() starts a fluent string: The object Str::of() returns: it holds a string and offers Laravel's string helpers as chainable methods, so trimming, changing case and cutting can be written one after another. whose methods chain like a collection’s. Number formats numbers through PHP’s intl extension, whose locale data decides the grouping:

Strings, numbers and dates for one fee reminder Python · formats_session.py
"""Strings, numbers and dates for a fee reminder: Str and fluent strings, Number with the en and en_IN locales, and
Carbon storing UTC while showing Asia/Kolkata. Files: the folder formats/ next to this file."""

from session import run

run(
    """
    php formats_probe.php
    """,
    app=True,
    overlay=("formats",),
)

Output

$ php formats_probe.php
-- strings
name as typed                     '  asha   VERMA '
Str::of()->squish()->title()      Asha Verma
Str::slug(title)                  fee-reminder-april-2031-batch
Str::limit(text, 24)              Your April instalment fo...
Str::mask(phone, *, 3, 5)         +91*****00001
Str::headline(column name)        Amount Paise

-- numbers
Number::format(en)                123,456.78
Number::format(en_IN)             1,23,456.78
Number::currency(INR, en_IN)      ₹1,23,456.78
Number::currency(INR, en)         ₹123,456.78
Number::percentage(0.875 * 100)   87.5%
Number::forHumans(12345678)       12 million
Number::abbreviate(12345678)      12M

-- dates
app timezone                      UTC
stored (UTC)                      2031-04-05 09:30:00
shown in Asia/Kolkata             2031-04-05 03:00 PM
due date + 10 days                2031-04-15
days between 5 Apr and 15 May     40

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 formats_session.py

The run squished a name typed as ' asha VERMA ' into Asha Verma, masked five digits of a phone number and cut a reminder to 24 characters. The same amount printed 123,456.78 in the en locale and 1,23,456.78 in en_IN, and the currency kept that grouping. Number::forHumans gives the short international form, 12 million.

Indian digit grouping and short forms

In India, digits are grouped as thousands, then lakh (1,00,000) and crore (1,00,00,000), which is what the en_IN locale prints, with the rupee sign from Number::currency(..., in: 'INR', locale: 'en_IN'). Number::forHumans has no lakh or crore option, so the short forms Indian readers expect need a small helper of your own.

Carbon stores UTC and shows the reader’s time

A new Laravel 13 project runs in UTC (config('app.timezone') printed UTC), and Carbon, the library behind now() and every date cast, keeps dates in that zone. Store in UTC and convert only when you show a value:

What the run did Result
A payment stored in UTC 2031-04-05 09:30:00
The same moment shown in Asia/Kolkata 2031-04-05 03:00 PM
A due date plus 10 days 2031-04-15
Days between two due dates 40

Carbon also does the calendar arithmetic, such as adding 10 days to a due date or counting the 40 days between two due dates in the run. India is 5 hours 30 minutes ahead of UTC, so dates near midnight change their day in India, and Carbon formats dates in Hindi as well. A daily report must cut its days at India’s midnight.

Checkpoint

Check: Str and Number with a locale · Store UTC and show local time

  1. Question 1 of 2 The amount 123456.78 is passed to Number::format with precision 2. Which output did the en_IN locale print in the run?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 1,23,456.78

    Number::format passes the locale to PHP's intl extension, so the same number prints 123,456.78 in en and 1,23,456.78 in en_IN.

  2. Question 2 of 2 A payment is stored at 09:30 UTC in a Laravel app whose time zone is UTC. What should a page for staff in India show, and how?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 03:00 PM, by converting with setTimezone('Asia/Kolkata') only when the value is shown

    Keep the app and the database in UTC and convert for display: 09:30 plus 5 hours 30 minutes is 15:00, which the run formatted as 03:00 PM.

Lazy collections and cursors for large inputs, with memory and queries measured Pro: opens with a Pro, Premium or Ultimate pass

A lazy collection keeps only the item it is working on, so a report over a large export runs in a few megabytes. This part measures that, shows the trap of reading one twice, and compares the two ways to stream a database table.

In this part:

  • A lazy collection pulls one item at a time from a generator
  • Reading a lazy collection twice runs its source twice
  • Streaming a table with cursor() or lazyById()
  • Exporting a year of payments without holding it in memory

2 runnable examples (PHP, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Short Indian amounts, Hindi slugs and dates, and the day boundaries of a UTC timestamp, recorded Pro: opens with a Pro, Premium or Ultimate pass

Laravel’s formatting helpers follow international defaults, and three of them surprise an app built for readers in India. This part records each surprise on the pinned toolchain and writes the fix with a test.

In this part:

  • Short Indian amounts need a helper of your own
  • Hindi titles lose their words in Str::slug()
  • Store UTC, cut days at India’s midnight, and keep dates immutable

2 runnable examples (IndianAmount.php, IndianAmountTest.php, Python, india_probe.php) · about 7 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise

Build Pathshala’s monthly collection report in your own project:

  1. Put the 12 instalment rows of this lesson in a collection, and print the total paid per batch and month with groupBy() and sum().
  2. Add a column that shows each total with Number::currency(..., in: 'INR', locale: 'en_IN').
  3. Store one payment at 09:30 UTC and print it in Asia/Kolkata with Carbon.

The questions below check the first three sections:

Practice that adapts to you

Practise this lesson

7 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 Which method keeps only the paid rows of a collection of instalments?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: filter() with a closure that returns true for a paid row

    filter selects, map transforms, groupBy splits, and sum and reduce fold a collection into one value.

  2. Question 2 of 7 A report should total overdue amounts per student, largest first. Which pipeline gives it?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: where('status', 'overdue')->groupBy('student')->map->sum('amount_paise')->sortDesc()

    Order the steps as the report reads: select, group, total, sort. The run's overdue line used the first three steps.

  3. Question 3 of 7 What does map->sum('amount_paise') do on a collection of groups?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: It calls sum('amount_paise') on each group and returns a collection of totals

    map->sum('amount_paise') is short for map(fn ($group) => $group->sum('amount_paise')).

  4. Question 4 of 7 A name arrives as ' asha VERMA '. Which call gives 'Asha Verma'?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: Str::of($name)->squish()->title()

    A fluent string chains string helpers one after another, the way a collection chains its methods.

  5. Question 5 of 7 Pathshala shows fees to parents in India and invoices to a partner school in the United States. What should change between the two documents?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: The locale passed to Number, en_IN for one and en for the other, while the amounts stay the same

    Keep money as integers and pass the reader's locale when formatting: the run printed the same amount as 123,456.78 in en and 1,23,456.78 in en_IN.

  6. Question 6 of 7 What time zone does a new Laravel 13 project use, as the run printed?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: UTC

    Laravel 13's skeleton sets the application time zone to UTC, which is the right zone to store times in.

  7. Question 7 of 7 A teammate proposes setting the app's time zone to Asia/Kolkata so pages show India's time without conversion. What is the main risk?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: Stored times would then be in India's time, so a second region, a server in UTC or a time change mixes two meanings in one column

    Store in UTC and convert at the edge, as the run did: the same stored value can then be shown in India's time, in another zone, or compared with a server log.

Interview questions

Warm-up: what is the difference between map() and each() on a Laravel collection?

map() returns a new collection with the result of the callback for every item, and leaves the original as it was. each() runs the callback for its side effect, returns the same collection, and stops early if the callback returns false. Use map() to transform values and each() to act on them.

These three take the lesson’s tools into harder cases:

  1. Mid to senior

    Pathshala's centres in India want a "collected today" figure on the dashboard and a daily total for the last 30 days. Payments are stored in UTC. A teammate groups by DATE(paid_at) in SQL. Explain what goes wrong, and design the query and the test.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    An accountant's "download all payments" button builds a CSV with Payment::all()->map(...) and fails with "Allowed memory size exhausted" once the table passes a few hundred thousand rows. Rewrite the export so its memory does not grow with the table, and say what you would measure.

    Model answer, Pro. First step and follow-ups

  3. Fresher to senior

    A course fee of 10,000 rupees is split into 3 equal instalments, and the report adds the instalments of 1,200 students. A developer stores amounts as floats in rupees and rounds when printing. Explain what goes wrong and how you would store, split and total the money.

    Model answer, Pro. First step and follow-ups

Flashcards · 7 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How does a collection turn rows into a report?

    collect($rows) wraps the array in an object whose chaining methods, such as groupBy() and map(), return a new collection and leave the original as it was, so the report reads top to bottom as its steps. Eloquent queries return collections too.

  2. What did groupBy(['batch', 'month']) and reduce() do in the fee report?

    groupBy nested the paid rows by batch and then by month, so an inner sum gave each batch's monthly total, 2,360.00 rupees for Maths Morning in April. reduce() folds a group into one value with your own closure.

  3. What does map->sum(...) mean on a collection?

    It is a higher-order message, short for a closure that calls sum on each item.

  4. Why did keyBy('student') turn 12 rows into 4?

    keyBy keeps one item per key, the last one, so each student kept only their last row. Use groupBy() when a key can repeat and keyBy() only for unique keys.

  5. Which Str helpers did the lesson use to clean a name, a phone number and a reminder?

    Str::of() starts a fluent string whose methods chain: squish() then title() turned a name typed with stray spaces and capitals into Asha Verma. Str::mask() hid five digits of a phone number and Str::limit() cut a reminder to 24 characters.

  6. How does Number::format print 123456.78 in the en and en_IN locales?

    123,456.78 in en and 1,23,456.78 in en_IN, Indian grouping by thousands, then lakh and crore, through PHP's intl extension. Number::currency(..., in: 'INR', locale: 'en_IN') keeps that grouping with the rupee sign; Number::forHumans has no lakh or crore form.

  7. In which time zone does a new Laravel 13 project keep dates, and when do you convert?

    UTC: config('app.timezone') prints UTC and Carbon keeps dates in it. Store in UTC and convert only to show a value; India is 5 hours 30 minutes ahead, so times near midnight change their date, and a daily report must cut its days at India's midnight.

Key takeaways

  • Collections chain small steps into a report: filter, map, groupBy and sum produced four batch-month totals from twelve rows, and keyBy keeps only the last row of a repeated key.
  • Str and fluent strings clean and shorten text, and Number formats numbers with the locale’s grouping, so en_IN prints 1,23,456.78.
  • Carbon keeps dates in UTC, the app’s time zone, and converts them only when a value is shown in India’s time.

Terms in this lesson

collection
A Laravel object that wraps an array and offers chainable methods such as filter, map, groupBy and sum, each returning a new collection. collect() makes one, and Eloquent queries return them.
fluent string
The object Str::of() returns: it holds a string and offers Laravel's string helpers as chainable methods, so trimming, changing case and cutting can be written one after another.
generator
A PHP function that uses yield to hand out values one at a time and pauses between them, instead of building and returning a whole array.
higher-order message
A short form for a collection method that calls the same method on each item: map->sum('amount_paise') means map with a closure that calls sum('amount_paise') on every item.
lazy collection
A collection that produces its items one at a time from a generator instead of holding an array, so its methods can work through an input larger than memory.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.