Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
HasDateFormatting
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 resolveDateCast
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 getDateOnlyFormat
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getDateFormats
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace DevToolbelt\LaravelEloquentPlus\Concerns;
6
7/**
8 * Trait for date/datetime formatting and output control.
9 *
10 * Provides functionality to control how date fields are returned:
11 * - As formatted strings (default)
12 * - As Carbon instances (when $carbonInstanceInFieldDates is true)
13 *
14 * @package DevToolbelt\LaravelEloquentPlus\Concerns
15 */
16trait HasDateFormatting
17{
18    /**
19     * The date formats for each date/datetime attribute.
20     *
21     * Maps attribute names to their output format string.
22     * Used by getAttribute() to return formatted strings instead of Carbon instances.
23     *
24     * @var array<string, string>
25     */
26    protected array $dateFormats = [];
27
28    /**
29     * Indicates if date fields should return Carbon instances instead of formatted strings.
30     *
31     * When false (default), date/datetime fields return formatted strings.
32     * When true, date/datetime fields return Carbon instances.
33     *
34     * @var bool
35     */
36    protected bool $carbonInstanceInFieldDates = false;
37
38    /**
39     * Resolve the appropriate date cast based on validation rules.
40     *
41     * Analyzes date-related validation rules to determine the correct cast:
42     * - 'date_format:Y-m-d' (date only) -> 'date' cast with Y-m-d format
43     * - 'date_format' with time component -> 'datetime' cast with specified format
44     * - 'date' (generic) -> 'datetime' cast with $dateFormat
45     *
46     * Also stores the output format in $dateFormats for use by getAttribute().
47     *
48     * @param string $attribute The attribute name
49     * @param array<int, mixed> $rules The validation rules for the attribute
50     * @return string|null The cast type ('date' or 'datetime'), or null if not a date field
51     */
52    protected function resolveDateCast(string $attribute, array $rules): ?string
53    {
54        $dateOnlyFormat = $this->getDateOnlyFormat();
55
56        foreach ($rules as $rule) {
57            if (!is_string($rule)) {
58                continue;
59            }
60
61            if (str_starts_with($rule, 'date_format:')) {
62                $format = substr($rule, strlen('date_format:'));
63                $this->dateFormats[$attribute] = $format;
64
65                if ($format === $dateOnlyFormat) {
66                    return 'date';
67                }
68
69                return 'datetime';
70            }
71        }
72
73        if (in_array('date', $rules, true)) {
74            $this->dateFormats[$attribute] = $this->dateFormat;
75            return 'datetime';
76        }
77
78        return null;
79    }
80
81    /**
82     * Get the date-only format extracted from $dateFormat.
83     *
84     * Extracts the date portion (before space) from the full datetime format.
85     * Falls back to the full format if no space is found.
86     *
87     * @return string The date-only format string
88     */
89    protected function getDateOnlyFormat(): string
90    {
91        if (str_contains($this->dateFormat, ' ')) {
92            return explode(' ', $this->dateFormat)[0];
93        }
94
95        return $this->dateFormat;
96    }
97
98    /**
99     * Get the date formats mapping.
100     *
101     * @return array<string, string>
102     */
103    public function getDateFormats(): array
104    {
105        return $this->dateFormats;
106    }
107}