Skip to content
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard.

## [3.1.4] 2025-10-16

* Fix - Handle array values correctly in the `get_*` query methods.

## [3.1.3] 2025-10-08

* Fix - Fix the `get_current_schema` method to cache the schema of each implementation.
* Fix - `get_current_schema` method will npw cache the schema of each implementation correctly.

[3.1.3]: https://github.com/stellarwp/schema/releases/tag/3.1.3

## [3.1.2] 2025-10-02

* Fix - Fix the `update_many` method to properly check if the transaction was successful.
* Fix - `update_many` method will now properly check if the transaction was successful.

[3.1.2]: https://github.com/stellarwp/schema/releases/tag/3.1.2

Expand Down
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ parameters:
level: 5
inferPrivatePropertyTypeFromConstructor: true
reportUnmatchedIgnoredErrors: false
treatPhpDocTypesAsCertain: false

# Paths to be analyzed.
paths:
Expand Down
77 changes: 59 additions & 18 deletions src/Schema/Traits/Custom_Table_Query_Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,9 @@ public static function get_total_items( array $args = [] ): int {
* Updates multiple rows into the table.
*
* @since 3.0.0
* @since 3.1.4 Enabled unfolding the value if is an array.
*
* @param array<mixed> $entries The entries to update.
* @param array<array<string, null|int|string|float|bool|DateTimeInterface|string[]|int[]|float[]|DateTimeInterface[]>> $entries The entries to update.
*
* @return bool Whether the update was successful.
*/
Expand Down Expand Up @@ -372,7 +373,7 @@ public static function update_many( array $entries ): bool {

[ $value, $placeholder ] = self::prepare_value_for_query( $column, $value );

$set_statement[] = $database::prepare( "%i = {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) );
$set_statement[] = $database::prepare( "%i = {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) );
}

$set_statement = implode( ', ', $set_statement );
Expand Down Expand Up @@ -672,12 +673,13 @@ protected static function get_join_parts( string $join_table, string $join_condi
*
* @since 3.0.0
* @since 3.1.1 Added the $order_by parameter.
* @since 3.1.4 Enabled unfolding the value if is an array.
*
* @param string $column The column to get the models by.
* @param mixed $value The value to get the models by.
* @param string $operator The operator to use.
* @param int $limit The limit of models to return.
* @param string $order_by The order by clause to use.
* @param string $column The column to get the models by.
* @param null|int|string|float|bool|DateTimeInterface|string[]|int[]|float[]|DateTimeInterface[] $value The value to get the models by.
* @param string $operator The operator to use.
* @param int $limit The limit of models to return.
* @param string $order_by The order by clause to use.
*
* @return mixed[] The models, or an empty array if no models are found.
*
Expand All @@ -690,12 +692,12 @@ public static function get_all_by( string $column, $value, string $operator = '=

$database = Config::get_db();
$results = [];
foreach ( static::fetch_all_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) ), $limit, ARRAY_A, $order_by ) as $task_array ) {
if ( empty( $task_array[ static::uid_column() ] ) ) {
foreach ( static::fetch_all_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) ), $limit, ARRAY_A, $order_by ) as $data_array ) {
if ( empty( $data_array[ static::uid_column() ] ) ) {
continue;
}

$results[] = static::transform_from_array( self::amend_value_types( $task_array ) );
$results[] = static::transform_from_array( self::amend_value_types( $data_array ) );
}

return $results;
Expand All @@ -705,34 +707,44 @@ public static function get_all_by( string $column, $value, string $operator = '=
* Gets the first model by a column.
*
* @since 3.0.0
* @since 3.1.4 Enabled unfolding the value if is an array.
* @since 3.1.4 Added the $operator parameter.
*
* @param string $column The column to get the model by.
* @param mixed $value The value to get the model by.
* @param string $column The column to get the model by.
* @param null|int|string|float|bool|DateTimeInterface|string[]|int[]|float[]|DateTimeInterface[] $value The value to get the model by.
* @param string $operator The operator to use.
*
* @return ?mixed The model, or `null` if no model is found.
*
* @throws InvalidArgumentException If the column does not exist.
* @throws InvalidArgumentException If the operator is invalid.
*/
public static function get_first_by( string $column, $value ) {
public static function get_first_by( string $column, $value, string $operator = '=' ) {
[ $value, $placeholder ] = self::prepare_value_for_query( $column, $value );

$operator = strtoupper( $operator );

if ( ! in_array( $operator, self::operators(), true ) ) {
throw new InvalidArgumentException( "Invalid operator: {$operator}." );
}

$database = Config::get_db();
$task_array = static::fetch_first_where( $database::prepare( "WHERE %i = {$placeholder}", ...array_filter( [ $column, $value ], static fn( $v ) => null !== $v ) ), ARRAY_A );
$data_array = static::fetch_first_where( $database::prepare( "WHERE %i {$operator} {$placeholder}", ...array_filter( [ $column, ...self::ensure_array( $value ) ], static fn( $v ) => null !== $v ) ), ARRAY_A );

if ( empty( $task_array[ static::uid_column() ] ) ) {
if ( empty( $data_array[ static::uid_column() ] ) ) {
return null;
}

return static::transform_from_array( self::amend_value_types( $task_array ) );
return static::transform_from_array( self::amend_value_types( $data_array ) );
}

/**
* Prepares a value for a query.
*
* @since 3.0.0
*
* @param string $column The column to prepare the value for.
* @param mixed $value The value to prepare.
* @param string $column The column to prepare the value for.
* @param null|string|int|float|bool|DateTimeInterface|array<null|string|int|float|bool|DateTimeInterface> $value The value to prepare.
*
* @return array{0: mixed, 1: string} The prepared value and placeholder.
*
Expand Down Expand Up @@ -791,6 +803,10 @@ private static function prepare_value_for_query( string $column, $value ): array
throw new InvalidArgumentException( "Unsupported column type: $column_type." );
}

if ( is_array( $value ) && ! $value ) {
return [ null, '(NULL)' ];
}

// @phpstan-ignore-next-line
return [ $value, is_array( $value ) ? '(' . implode( ',', array_fill( 0, count( $value ), $placeholder ) ) . ')' : $placeholder ];
}
Expand Down Expand Up @@ -918,4 +934,29 @@ public static function cast_value_based_on_type( string $type, $value ) {
throw new InvalidArgumentException( "Unsupported column type: {$type}." );
}
}

/**
* Ensures the value is an array.
*
* @since 3.1.4
*
* @param null|string|int|float|bool|DateTimeInterface|string[]|int[]|float[]|DateTimeInterface[] $value The value to ensure is an array.
*
* @return array<null|string|int|float|bool|DateTimeInterface> The value as an array.
*/
private static function ensure_array( $value ): array {
if ( null !== $value && ! is_int( $value ) && ! is_string( $value ) && ! is_float( $value ) && ! is_bool( $value ) && ! $value instanceof DateTimeInterface && ! is_array( $value ) ) {
throw new InvalidArgumentException( 'Value should be an integer, string, float, boolean, DateTimeInterface, or array.' );
}

if ( is_array( $value ) && $value ) {
foreach ( $value as $k => $v ) {
if ( null !== $v && ! is_int( $v ) && ! is_string( $v ) && ! is_float( $v ) && ! is_bool( $v ) && ! $v instanceof DateTimeInterface ) {
throw new InvalidArgumentException( 'Value with offset ' . $k . ' should be an integer, string, float, boolean or DateTimeInterface.' );
}
}
}

return is_array( $value ) ? $value : [ $value ];
}
}
Loading