Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
245cb65
Fix for issue #35. Corrected ActiveRecordTest's so that capitalizatio…
ipundit Aug 31, 2023
e1bb78f
Primary key has to be lowercased too because gettter/setters are lowe…
ipundit Aug 31, 2023
f6e2981
Fix for issue #35. Corrected ActiveRecordTest's so that capitalizatio…
ipundit Aug 31, 2023
67471f5
Primary key has to be lowercased too because gettter/setters are lowe…
ipundit Aug 31, 2023
83a4db0
Merge branch 'master' of https://github.com/ipundit/activerecord
ipundit Sep 1, 2023
1aea4e1
Merge branch 'master' into master
ipundit Sep 1, 2023
9639f31
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 2, 2023
52478ab
Running all the tests may take more than the default of 300 seconds. …
ipundit Sep 2, 2023
c323677
Write proper tests that demonstrate the fix for issue #35:
ipundit Sep 2, 2023
80ed052
Switched from firstName field to existing mixedCaseField to pass Post…
ipundit Sep 2, 2023
b8ecfc0
- List things in alphabetical order
ipundit Sep 2, 2023
23d00a7
Revert updates to test suite as ActiveRecordTest::test_case_insensiti…
ipundit Sep 2, 2023
e31ec4f
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 2, 2023
9a6fb32
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 2, 2023
ba7f090
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
d42a7d1
Implemented composer test <fileName> <filter>
ipundit Sep 4, 2023
fc79d97
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
f6f67ee
Sketch out an idea for a Model::find() replacement
ipundit Sep 4, 2023
568cfb5
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
650d368
Fix style issues
ipundit Sep 4, 2023
edea071
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
767e537
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
d140a01
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 4, 2023
75919a7
Fix PHP Stan level 7 errors in SQLExecutionPlan.php
ipundit Sep 5, 2023
19292fb
Minor coding style fixes to pass regression.
ipundit Sep 5, 2023
089d3d3
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
c4e55dc
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
2eec6b0
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
1f76eac
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
65e7ff3
Implemented last() chainable module
ipundit Sep 5, 2023
6aba443
Fix for issue #55
ipundit Sep 5, 2023
c772578
Remove unused scripts and files references.
ipundit Sep 5, 2023
8291ea6
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
cf6056e
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
64e45b8
Remove unused items in composer.json
ipundit Sep 5, 2023
7efb255
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
1dcad8d
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
90350f4
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
7b1e20e
Merge branch 'master' of https://github.com/ipundit/activerecord
ipundit Sep 5, 2023
b4c8acb
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 5, 2023
5fc51d0
Merge branch 'master' of https://github.com/ipundit/activerecord into…
ipundit Sep 5, 2023
a58b20c
Style fixes
ipundit Sep 5, 2023
0b200e6
PHP Stan fixes
ipundit Sep 5, 2023
c42cdf4
Renamed SQLExecutionPlan to Relation
ipundit Sep 5, 2023
e9030cd
Renamed $sqlPlan to $relation
ipundit Sep 5, 2023
60353de
- Make chainable function spelling the same as Rails4, so joins() rat…
ipundit Sep 6, 2023
d946176
Moved implementation of count() from Model to Relation
ipundit Sep 6, 2023
05fad21
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 6, 2023
3170e85
Hide $this->table behind a getter to lazily load the table.
ipundit Sep 6, 2023
8264358
Implemented find()
ipundit Sep 6, 2023
4bb1e1c
Removed public static function find_by_pk() because it's not called i…
ipundit Sep 6, 2023
365c66c
Implemented find() and where()
ipundit Sep 6, 2023
0b81ba0
Merge branch 'master' of https://github.com/php-activerecord/activere…
ipundit Sep 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 146 additions & 124 deletions lib/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -1492,25 +1492,6 @@ public function reset_dirty(): void
$this->__dirty = [];
}

/**
* A list of valid finder options.
*
* @var array<string>
*/
public static array $VALID_OPTIONS = [
'conditions',
'limit',
'offset',
'order',
'select',
'joins',
'include',
'readonly',
'group',
'from',
'having'
];

/**
* Enables the use of dynamic finders.
*
Expand Down Expand Up @@ -1622,24 +1603,118 @@ public function __call($method, $args)
throw new ActiveRecordException("Call to undefined method: $method");
}

public static function select(string $columns='*'): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->select($columns);

return $relation;
}

/**
* @param string|array<string> $joins
*/
public static function joins(string|array $joins): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->joins($joins);

return $relation;
}

public static function order(string $order): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->order($order);

return $relation;
}

public static function group(string $columns): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->group($columns);

return $relation;
}

public static function limit(int $limit): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->limit($limit);

return $relation;
}

public static function offset(int $offset): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->offset($offset);

return $relation;
}

public static function having(string $having): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->having($having);

return $relation;
}

public static function from(string $from): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->from($from);

return $relation;
}

/**
* @param string|array<string|mixed> $include
*/
public static function include(string|array $include): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);
$relation->include($include);

return $relation;
}

/**
* @param string|array<string|mixed> $where
*/
public static function where(string|array $where): Relation
{
$relation = new Relation(get_called_class(), static::$alias_attribute);

return $relation->where($where);
}

/**
* Alias for self::find('all').
* needle is one of:
*
* @see find
* array of primary keys all([1, 3, 5, 8]) WHERE author_id in (1, 3, 5, 8)
* mapping of column names all(["name"=>"Philip", "publisher"=>"Random House"]) WHERE name=Philip AND publisher=Random House
* raw WHERE statement all(['name = (?) and publisher <> (?)', 'Bill Clinton', 'Random House'])
*
* @param array<number|mixed|string> $needle An array containing values for the pk
*
* @return array<int,static>
* @return array<Model> All the rows that matches query. If no rows match, returns []
*/
public static function all(/* ... */): array
public static function all(array $needle = []): array
Copy link
Contributor

@shmax shmax Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all doesn't take any arguments; I think it's only meant as a means to get a handle to the Relation without executing anything:

$allBooks = Books::all();

...
$books = $allBooks->where(['title'=>'foo'].to_a()'

{
/* @phpstan-ignore-next-line */
return static::find('all', ...func_get_args());
$relation = new Relation(get_called_class(), static::$alias_attribute);

return $relation->all($needle);
}

/**
* Get a count of qualifying records.
*
* ```
* YourModel::count(['conditions' => 'amount > 3.14159265']);
* YourModel::count('amount > 3.14159265');
* YourModel::count(['name' => 'Tito', 'author_id' => 1]));
* ```
*
* @see find
Expand All @@ -1650,7 +1725,6 @@ public static function count(/* ... */): int
{
$args = func_get_args();
$options = static::extract_and_validate_options($args);
$options['select'] = 'COUNT(*)';

if (!empty($args) && !is_null($args[0]) && !empty($args[0])) {
if (is_hash($args[0])) {
Expand All @@ -1660,13 +1734,13 @@ public static function count(/* ... */): int
}
}

$table = static::table();
$sql = $table->options_to_sql($options);
$values = $sql->get_where_values();
$relation = new Relation(get_called_class(), static::$alias_attribute);

$res = static::connection()->query_and_fetch_one($sql->to_s(), $values);
if (0 === count($options)) {
$options['conditions'] = [];
}

return $res;
return $relation->count($options['conditions']);
}

/**
Expand Down Expand Up @@ -1766,22 +1840,22 @@ public static function last(/* ... */): static|null
*
* @throws RecordNotFound if no options are passed or finding by pk and no records matched
*
* @return static|static[]|null
* @return Model|Model[]|null
*
* The rules for what gets returned are complex, but predictable:
*
* /-------------------------------------------------------------------------------------------
* First Argument Return Type Example
* --------------------------------------------------------------------------------------------
* int|string static User::find(3);
* array<string, int|string> static User::find(["name"=>"Philip"]);
* "first" static|null User::find("first", ["name"=>"Waldo"]);
* "last" static|null User::find("last", ["name"=>"William"]);
* "all" static[] User::find("all", ["name"=>"Stephen"]
* ...int|string static[] User::find(1, 3, 5, 8);
* array<int,int|string> static[] User::find([1,3,5,8]);
* int|string Model User::find(3);
* array<string, int|string> Model User::find(["name"=>"Philip"]);
* "first" Model|null User::find("first", ["name"=>"Waldo"]);
* "last" Model|null User::find("last", ["name"=>"William"]);
* "all" Model[] User::find("all", ["name"=>"Stephen"]
* ...int|string Model[] User::find(1, 3, 5, 8);
* array<int,int|string> Model[] User::find([1,3,5,8]);
*/
public static function find(/* $type, $options */): static|array|null
public static function find(/* $type, $options */): Model|array|null
{
$class = get_called_class();

Expand All @@ -1791,6 +1865,15 @@ public static function find(/* $type, $options */): static|array|null
}
$options = static::extract_and_validate_options($args);

$relation = new Relation(get_called_class(), static::$alias_attribute);

foreach (Relation::$VALID_OPTIONS as $key) {
if ('conditions'!= $key && array_key_exists($key, $options)) {
$relation->$key($options[$key]);
unset($options[$key]);
}
}

$num_args = count($args);
$single = true;

Expand All @@ -1801,18 +1884,13 @@ public static function find(/* $type, $options */): static|array|null
break;

case 'last':
if (!array_key_exists('order', $options)) {
$options['order'] = join(' DESC, ', static::table()->pk) . ' DESC';
} else {
$options['order'] = SQLBuilder::reverse_order($options['order']);
}
$relation->last(1);

// fall thru

// no break
case 'first':
$options['limit'] = 1;
$options['offset'] = 0;
$relation->limit(1);
$relation->offset(0);
break;
}

Expand All @@ -1831,86 +1909,30 @@ public static function find(/* $type, $options */): static|array|null
}
}

// anything left in $args is a find by pk
if ($num_args > 0 && !isset($options['conditions'])) {
$list = static::find_by_pk($args, $options, true);
} else {
$options['mapped_names'] = static::$alias_attribute;
$list = static::table()->find($options);
}

return $single ? ($list[0] ?? null) : $list;
}

/**
* Will look up a list of primary keys from cache
*
* @param array<PrimaryKey> $pks An array of primary keys
*
* @return array<Model>
*/
protected static function get_models_from_cache(array $pks): array
{
$models = [];
$table = static::table();

foreach ($pks as $pk) {
$options = ['conditions' => static::pk_conditions($pk)];
$models[] = Cache::get($table->cache_key_for_model($pk), function () use ($table, $options) {
$res = $table->find($options);

return $res ? $res[0] : [];
}, $table->cache_model_expire);
}

return array_filter($models);
}

/**
* Finder method which will find by a single or array of primary keys for this model.
*
* @see find
*
* @param PrimaryKey $values An array containing values for the pk
* @param array<mixed> $options An options array
*
* @throws RecordNotFound if a record could not be found
*
* @return Model|Model[]
*/
public static function find_by_pk(array|string|int|null $values, array $options, bool $forceArray = false): Model|array
{
$single = !is_array($values) && !$forceArray;
if (null === $values) {
throw new RecordNotFound("Couldn't find " . get_called_class() . ' without an ID');
}

$table = static::table();

if ($table->cache_individual_model ?? false) {
$pks = is_array($values) ? $values : [$values];
$list = static::get_models_from_cache($pks);
} else {
// int|string|array<int|string>
$options['conditions'] = static::pk_conditions($values);
$list = $table->find($options);
if (is_array($args) && 0 === count($args)) {
if (array_key_exists('conditions', $options) && $single) {
$args = $options['conditions'];
if (!is_array($args)) {
$args = [$args];
}
} else {
$args = $options;
}
}
$results = count($list);

if ($results != ($expected = @count((array) $values))) {
$class = get_called_class();
if (is_array($values)) {
$values = join(',', $values);
if ($single) {
if (1 === $num_args && is_array($args) && array_is_list($args)) {
$args = $args[0];
}

if (1 == $expected) {
throw new RecordNotFound("Couldn't find $class with ID=$values");
}
return $relation->whereToBeReplacedByFind($args, true);
}

throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
if (array_key_exists('conditions', $options)) {
$args = $options;
}

return $single ? $list[0] : $list;
return $relation->all($args);
}

/**
Expand Down Expand Up @@ -1954,12 +1976,12 @@ public static function is_options_hash(mixed $options, bool $throw = true): bool
{
if (is_hash($options)) {
$keys = array_keys($options);
$diff = array_diff($keys, self::$VALID_OPTIONS);
$diff = array_diff($keys, Relation::$VALID_OPTIONS);

if (!empty($diff) && $throw) {
throw new ActiveRecordException('Unknown key(s): ' . join(', ', $diff));
}
$intersect = array_intersect($keys, self::$VALID_OPTIONS);
$intersect = array_intersect($keys, Relation::$VALID_OPTIONS);

if (!empty($intersect)) {
return true;
Expand Down
Loading