Skip to content
Draft
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,45 @@ composer require mll-lab/php-utils

See [tests](tests).

### SafeCast

PHP's native type casts like `(int)` and `(float)` can produce unexpected results, especially when casting from strings.
The `SafeCast` utility provides safe alternatives that validate input before casting:

Each type has two variants:
- `toX()`: returns the cast value or throws `\InvalidArgumentException`
- `tryX()`: returns the cast value or `null` (like `Enum::tryFrom()`)

```php
use MLL\Utils\SafeCast;

// Safe integer casting
SafeCast::toInt(42); // 42
SafeCast::toInt('42'); // 42
SafeCast::toInt('hello'); // throws InvalidArgumentException
SafeCast::tryInt('hello'); // null

// Safe float casting
SafeCast::toFloat(3.14); // 3.14
SafeCast::toFloat('3.14'); // 3.14
SafeCast::toFloat('abc'); // throws InvalidArgumentException
SafeCast::tryFloat('abc'); // null

// Safe string casting
SafeCast::toString(42); // '42'
SafeCast::toString(null); // ''
SafeCast::tryString([1, 2]); // null

// Safe boolean casting
SafeCast::toBool(true); // true
SafeCast::toBool(1); // true
SafeCast::toBool('0'); // false
SafeCast::toBool('true'); // throws InvalidArgumentException
SafeCast::tryBool('true'); // null
```

See [tests](tests/SafeCastTest.php) for more examples.

### Holidays

You can add custom holidays by registering a method that returns a map of holidays for a given year.
Expand Down
7 changes: 2 additions & 5 deletions src/LightcyclerExportSheet/LightcyclerDataParsingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MLL\Utils\LightcyclerExportSheet;

use Illuminate\Support\Collection;
use MLL\Utils\SafeCast;

trait LightcyclerDataParsingTrait
{
Expand All @@ -14,11 +15,7 @@ protected function parseFloatValue(?string $value): ?float
return null;
}

if (! is_numeric($cleanString)) {
throw new \InvalidArgumentException("Invalid float value: '{$cleanString}'");
}

return (float) $cleanString;
return SafeCast::toFloat($cleanString);
}

/** @return array{float, float} */
Expand Down
3 changes: 2 additions & 1 deletion src/LightcyclerExportSheet/LightcyclerXmlParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Collection;
use MLL\Utils\Microplate\Coordinates;
use MLL\Utils\Microplate\CoordinateSystem12x8;
use MLL\Utils\SafeCast;

use function Safe\simplexml_load_string;

Expand Down Expand Up @@ -77,7 +78,7 @@ private function extractPropertiesFromXml(\SimpleXMLElement $xmlElement): array
$properties = [];

foreach ($xmlElement->prop as $propertyNode) {
$propertyName = (string) $propertyNode->attributes()->name;
$propertyName = SafeCast::toString($propertyNode->attributes()->name);
$propertyValue = $propertyNode->__toString();

if (! isset($properties[$propertyName])) {
Expand Down
7 changes: 6 additions & 1 deletion src/LightcyclerSampleSheet/AbsoluteQuantificationSample.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use MLL\Utils\Microplate\Coordinates;
use MLL\Utils\Microplate\CoordinateSystem12x8;
use MLL\Utils\SafeCast;

class AbsoluteQuantificationSample
{
Expand Down Expand Up @@ -42,7 +43,11 @@ public static function formatConcentration(?int $concentration): ?string
return null;
}

$exponent = (int) floor(log10(abs($concentration)));
if ($concentration === 0) {
return '0.00E0';
}

$exponent = SafeCast::toInt(floor(log10(abs($concentration))));
$mantissa = $concentration / (10 ** $exponent);

return number_format($mantissa, 2) . 'E' . $exponent;
Expand Down
9 changes: 5 additions & 4 deletions src/Microplate/CoordinateSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Arr;
use MLL\Utils\Microplate\Enums\FlowDirection;
use MLL\Utils\Microplate\Exceptions\UnexpectedFlowDirection;
use MLL\Utils\SafeCast;

/**
* Children should be called `CoordinateSystemXxY`, where X is the number of columns and Y is the number of rows.
Expand Down Expand Up @@ -36,14 +37,14 @@ public function paddedColumns(): array
/** 0-pad column to be as long as the longest column in the coordinate system. */
public function padColumn(int $column): string
{
$maxColumnLength = strlen((string) $this->columnsCount());
$maxColumnLength = strlen(SafeCast::toString($this->columnsCount()));

return str_pad((string) $column, $maxColumnLength, '0', STR_PAD_LEFT);
return str_pad(SafeCast::toString($column), $maxColumnLength, '0', STR_PAD_LEFT);
}

public function rowForRowFlowPosition(int $position): string
{
$index = (int) floor(($position - 1) / $this->columnsCount());
$index = SafeCast::toInt(floor(($position - 1) / $this->columnsCount()));

return $this->rows()[$index];
}
Expand All @@ -60,7 +61,7 @@ public function columnForRowFlowPosition(int $position): int

public function columnForColumnFlowPosition(int $position): int
{
$index = (int) floor(($position - 1) / $this->rowsCount());
$index = SafeCast::toInt(floor(($position - 1) / $this->rowsCount()));

return $this->columns()[$index];
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microplate/Coordinates.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Arr;
use MLL\Utils\Microplate\Enums\FlowDirection;
use MLL\Utils\Microplate\Exceptions\UnexpectedFlowDirection;
use MLL\Utils\SafeCast;

use function Safe\preg_match;

Expand Down Expand Up @@ -89,7 +90,7 @@ public static function fromString(string $coordinatesString, CoordinateSystem $c
}
/** @var array{1: string, 2: string} $matches */

return new static($matches[1], (int) $matches[2], $coordinateSystem);
return new static($matches[1], SafeCast::toInt($matches[2]), $coordinateSystem);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Microplate/FullColumnSection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use MLL\Utils\Microplate\Exceptions\MicroplateIsFullException;
use MLL\Utils\Microplate\Exceptions\SectionIsFullException;
use MLL\Utils\SafeCast;

/**
* A section that occupies all wells of a column if one sample exists in this column.
Expand Down Expand Up @@ -90,6 +91,6 @@ private function sectionCanGrow(): bool

private function reservedColumns(): int
{
return (int) ceil($this->sectionItems->count() / $this->sectionedMicroplate->coordinateSystem->rowsCount());
return SafeCast::toInt(ceil($this->sectionItems->count() / $this->sectionedMicroplate->coordinateSystem->rowsCount()));
}
}
3 changes: 2 additions & 1 deletion src/Microplate/MicroplateSet/MicroplateSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use MLL\Utils\Microplate\Coordinates;
use MLL\Utils\Microplate\CoordinateSystem;
use MLL\Utils\Microplate\Enums\FlowDirection;
use MLL\Utils\SafeCast;

/**
* @template TCoordinateSystem of CoordinateSystem
Expand Down Expand Up @@ -41,7 +42,7 @@ public function locationFromPosition(int $setPosition, FlowDirection $direction)
throw new \OutOfRangeException("Expected a position between 1-{$positionsCount}, got: {$setPosition}.");
}

$plateIndex = (int) floor(($setPosition - 1) / $this->coordinateSystem->positionsCount());
$plateIndex = SafeCast::toInt(floor(($setPosition - 1) / $this->coordinateSystem->positionsCount()));
$positionOnSinglePlate = $setPosition - ($plateIndex * $this->coordinateSystem->positionsCount());

return new Location(
Expand Down
Loading