Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3651245
Accept enum disk names in filesystem manager operations
binaryfire Oct 30, 2026
2b031f8
Extract JSON paths before compiling overlap comparisons
binaryfire Oct 30, 2026
aa0ca78
Load missing nested morph relationships by model type
binaryfire Oct 30, 2026
d27e493
Preserve related model identity in relationship helpers
binaryfire Oct 30, 2026
6d29b59
Keep recycled factory relationships on their derived definitions
binaryfire Oct 30, 2026
409fa04
Replay morph relation constraints in existence queries
binaryfire Oct 30, 2026
73e09fe
Separate compiled Blade views by their active echo format
binaryfire Oct 30, 2026
84a1d2b
Recover Blade error sources from compiled file metadata
binaryfire Oct 30, 2026
93b9ecd
Preserve cancellation cleanup failures without duplicate or lost reports
binaryfire Oct 30, 2026
f96642b
Keep exception reporting decisions consistent across later checks
binaryfire Oct 30, 2026
ea12fa2
Keep original source paths in exception and view diagnostics
binaryfire Oct 30, 2026
3ced2b7
Preserve external view identities in relative compiled caches
binaryfire Oct 30, 2026
9bf05e3
Preserve paths and URLs in console output
binaryfire Oct 30, 2026
70940f7
Ignore form metadata when rejecting unknown request fields
binaryfire Oct 30, 2026
610eb7c
Clarify the uncached enum disk purge regression
binaryfire Oct 30, 2026
5061c92
Keep relative Blade paths distinct in compiled caches
binaryfire Oct 30, 2026
0d2092b
Read Blade source metadata from the final compiler footer
binaryfire Oct 30, 2026
cc3eb74
Preserve cancellation during console timezone resolution
binaryfire Oct 30, 2026
cc24797
Purge the default filesystem for an empty disk name
binaryfire Oct 30, 2026
bfb011c
Describe the console relative-path data provider
binaryfire Oct 30, 2026
375c67e
Check the serialized inline attachment identifier
binaryfire Oct 30, 2026
212cd9b
Preserve the explicit empty-name filesystem purge contract
binaryfire Oct 30, 2026
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
3 changes: 2 additions & 1 deletion src/console/src/Commands/ScheduleListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Hypervel\Support\Arr;
use Hypervel\Support\CarbonImmutable;
use Hypervel\Support\Collection;
use Hypervel\Support\Str;
use ReflectionClass;
use ReflectionFunction;
use Symfony\Component\Console\Attribute\AsCommand;
Expand Down Expand Up @@ -333,7 +334,7 @@ private function getClosureLocation(CallbackEvent $event): string

return sprintf(
'%s:%s',
str_replace($this->hypervel->basePath() . DIRECTORY_SEPARATOR, '', $function->getFileName() ?: ''),
Str::replaceStart($this->hypervel->basePath() . DIRECTORY_SEPARATOR, '', $function->getFileName() ?: ''),
$function->getStartLine()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class EnsureRelativePaths
public function __invoke(string $string): string
{
if (function_exists('app') && app()->has('path.base')) {
$string = str_replace(base_path() . '/', '', $string);
// Shorten standalone paths without removing part of another path or URL.
$string = (string) preg_replace('#(?<![^\s\[({<>\'"])' . preg_quote(base_path() . '/', '#') . '#', '', $string);
}

return $string;
Expand Down
6 changes: 5 additions & 1 deletion src/database/src/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
use Closure;
use Exception;
use Hypervel\Contracts\Database\Eloquent\Builder as BuilderContract;
use Hypervel\Contracts\Database\Eloquent\SupportsPartialRelations;
use Hypervel\Contracts\Database\Query\Expression;
use Hypervel\Contracts\Support\Arrayable;
use Hypervel\Database\BinaryParameter;
use Hypervel\Database\Concerns\BuildsQueries;
use Hypervel\Database\Eloquent\Concerns\QueriesRelationships;
use Hypervel\Database\Eloquent\Relations\BelongsToMany;
use Hypervel\Database\Eloquent\Relations\HasOneOrManyThrough;
use Hypervel\Database\Eloquent\Relations\Relation;
use Hypervel\Database\MultipleRecordsFoundException;
use Hypervel\Database\Query\Builder as QueryBuilder;
Expand Down Expand Up @@ -755,7 +757,7 @@ public function updateOrCreate(array $attributes, Closure|array $values = []): M
*/
public function incrementOrCreate(array $attributes, string $column = 'count', float|int $default = 1, float|int $step = 1, array $extra = []): Model
{
return tap($this->firstOrCreate($attributes, [$column => $default]), function ($instance) use ($column, $step, $extra) {
return tap($this->firstOrCreate($attributes, array_merge($extra, [$column => $default])), function ($instance) use ($column, $step, $extra) {
if (! $instance->wasRecentlyCreated) {
$instance->increment($column, $step, $extra); // @phpstan-ignore method.protected (handled by __call)
}
Expand Down Expand Up @@ -1840,6 +1842,8 @@ protected function createSelectWithConstraint(string $name): array
return [explode(':', $name)[0], static function ($query) use ($name) {
$query->select(array_map(static function ($column) use ($query) {
return $query instanceof BelongsToMany
|| $query instanceof HasOneOrManyThrough
|| ($query instanceof SupportsPartialRelations && $query->isOneOfMany())
? $query->getRelated()->qualifyColumn($column)
: $column;
}, explode(',', explode(':', $name)[1])));
Expand Down
6 changes: 5 additions & 1 deletion src/database/src/Eloquent/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,11 @@ protected function loadMissingRelation(self $models, array $path): void
$models = $models->collapse();
}

$this->loadMissingRelation(new static($models), $path);
$models->groupBy(fn (Model $model): string => $model::class)->each(
function (BaseCollection $models) use ($path): void {
$this->loadMissingRelation(new static($models), $path);
}
);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/database/src/Eloquent/Concerns/QueriesRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ protected function getBelongsToRelation(MorphTo $relation, string $type): Belong
);
});

$relation->replayMacros($belongsTo->getQuery());

$belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());

// @phpstan-ignore return.type (TModel IS TDeclaringModel in this context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ public function createFor(Model $model): void

/**
* Specify the model instances to always use when creating relationships.
*
* @return $this
*/
public function recycle(Collection $recycle): static
{
$relationship = clone $this;

if ($this->factory instanceof Factory) {
$this->factory = $this->factory->recycle($recycle);
$relationship->factory = $this->factory->recycle($recycle);
}

return $this;
return $relationship;
}
}
9 changes: 5 additions & 4 deletions src/database/src/Eloquent/Factories/BelongsToRelationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,16 @@ protected function resolver(?string $key): Closure

/**
* Specify the model instances to always use when creating relationships.
*
* @return $this
*/
public function recycle(Collection $recycle): static
{
$relationship = clone $this;

if ($this->factory instanceof Factory) {
$this->factory = $this->factory->recycle($recycle);
$relationship->factory = $this->factory->recycle($recycle);
$relationship->resolved = null;
}

return $this;
return $relationship;
}
}
75 changes: 49 additions & 26 deletions src/database/src/Eloquent/Factories/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ protected function createChildren(Model $model): void
{
Model::unguarded(function () use ($model) {
$this->has->each(function ($has) use ($model) {
$has->recycle($this->recycle)->createFor($model);
$has->createFor($model);
});
});
}
Expand Down Expand Up @@ -536,7 +536,7 @@ protected function getRawAttributes(?Model $parent): array
protected function parentResolvers(): array
{
return $this->for
->map(fn (BelongsToRelationship $for) => $for->recycle($this->recycle)->attributesFor($this->newModel()))
->map(fn (BelongsToRelationship $for) => $for->attributesFor($this->newModel()))
->collapse()
->all();
}
Expand Down Expand Up @@ -598,7 +598,7 @@ public function state(callable|array $state): static
public function prependState(callable|array $state): static
{
return $this->newInstance([
'states' => $this->states->prepend(
'states' => (new Collection($this->states))->prepend(
is_callable($state) ? $state : fn () => $state,
),
]);
Expand Down Expand Up @@ -641,11 +641,17 @@ public function crossJoinSequence(array ...$sequence): static
*/
public function has(self $factory, ?string $relationship = null): static
{
$definition = new Relationship(
$factory,
$relationship ?? $this->guessRelationship($factory->modelName())
);

if ($this->recycle->isNotEmpty()) {
$definition = $definition->recycle($this->recycle);
}

return $this->newInstance([
'has' => $this->has->concat([new Relationship(
$factory,
$relationship ?? $this->guessRelationship($factory->modelName())
)]),
'has' => $this->has->concat([$definition]),
]);
}

Expand Down Expand Up @@ -674,45 +680,62 @@ public function hasAttached(self|Collection|Model|array $factory, callable|array
$pivot = new Sequence(...$pivot);
}

return $this->newInstance([
'has' => $this->has->concat([new BelongsToManyRelationship(
$factory,
$pivot,
$relationship ?? StrCache::camel(StrCache::plural(class_basename(
$factory instanceof Factory
? $factory->modelName()
: Collection::wrap($factory)->first()
)))
)]),
]);
$definition = new BelongsToManyRelationship(
$factory,
$pivot,
$relationship ?? StrCache::camel(StrCache::plural(class_basename(
$factory instanceof Factory
? $factory->modelName()
: Collection::wrap($factory)->first()
)))
);

if ($this->recycle->isNotEmpty()) {
$definition = $definition->recycle($this->recycle);
}

return $this->newInstance(['has' => $this->has->concat([$definition])]);
}

/**
* Define a parent relationship for the model.
*/
public function for(self|Model $factory, ?string $relationship = null): static
{
return $this->newInstance(['for' => $this->for->concat([new BelongsToRelationship(
$definition = new BelongsToRelationship(
$factory,
$relationship ?? StrCache::camel(class_basename(
$factory instanceof Factory ? $factory->modelName() : $factory
))
)])]);
);

if ($this->recycle->isNotEmpty()) {
$definition = $definition->recycle($this->recycle);
}

return $this->newInstance(['for' => $this->for->concat([$definition])]);
}

/**
* Provide model instances to use instead of any nested factory calls when creating relationships.
*/
public function recycle(Model|Collection|array $model): static
{
$models = Collection::wrap($model instanceof Model ? func_get_args() : $model)->flatten();

if ($models->isEmpty()) {
return $this->newInstance();
}

// Group provided models by the type and merge them into existing recycle collection
$recycle = $this->recycle->flatten()->merge($models)->groupBy(fn ($model) => get_class($model));

// Recycle relationship copies when configured, not per model: counted groups
// share one parent without leaking it or repeatedly merging the same candidates.
return $this->newInstance([
'recycle' => $this->recycle
->flatten()
->merge(
Collection::wrap($model instanceof Model ? func_get_args() : $model)
->flatten()
)->groupBy(fn ($model) => get_class($model)),
'recycle' => $recycle,
'for' => $this->for->map(fn (BelongsToRelationship $relationship): BelongsToRelationship => $relationship->recycle($models)),
'has' => $this->has->map(fn (BelongsToManyRelationship|Relationship $relationship): BelongsToManyRelationship|Relationship => $relationship->recycle($models)),
]);
}

Expand Down
8 changes: 4 additions & 4 deletions src/database/src/Eloquent/Factories/Relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ public function createFor(Model $parent): void

/**
* Specify the model instances to always use when creating relationships.
*
* @return $this
*/
public function recycle(Collection $recycle): static
{
$this->factory = $this->factory->recycle($recycle);
$relationship = clone $this;

return $this;
$relationship->factory = $this->factory->recycle($recycle);

return $relationship;
}
}
35 changes: 35 additions & 0 deletions src/database/src/Eloquent/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,20 @@ public function updateOrCreate(array $attributes, Closure|array $values = [], ar
});
}

/**
* Create a related record matching the attributes, or increment the existing record.
*
* @return TRelatedModel
*/
public function incrementOrCreate(array $attributes, string $column = 'count', float|int $default = 1, float|int $step = 1, array $extra = []): Model
{
return tap($this->firstOrCreate($attributes, array_merge($extra, [$column => $default])), function (Model $instance) use ($column, $step, $extra): void {
if (! $instance->wasRecentlyCreated) {
$instance->increment($column, $step, $extra); // @phpstan-ignore method.protected (handled by __call)
}
});
}

/**
* Find a related model by its primary key.
*
Expand Down Expand Up @@ -1146,6 +1160,27 @@ public function each(callable $callback, int $count = 1000): bool
});
}

/**
* Run a map over each item while chunking.
*
* @template TReturn
*
* @param callable(object{pivot: TPivotModel}&TRelatedModel): TReturn $callback
* @return BaseCollection<int, TReturn>
*/
public function chunkMap(callable $callback, int $count = 1000): BaseCollection
{
$collection = new BaseCollection;

$this->chunk($count, function (BaseCollection $items) use ($collection, $callback): void {
$items->each(function (mixed $item) use ($collection, $callback): void {
$collection->push($callback($item));
});
});

return $collection;
}

/**
* Query lazily, by chunks of the given size.
*
Expand Down
14 changes: 14 additions & 0 deletions src/database/src/Eloquent/Relations/HasOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,20 @@ public function updateOrCreate(array $attributes, Closure|array $values = []): M
});
}

/**
* Create a related record matching the attributes, or increment the existing record.
*
* @return TRelatedModel
*/
public function incrementOrCreate(array $attributes, string $column = 'count', float|int $default = 1, float|int $step = 1, array $extra = []): Model
{
return tap($this->firstOrCreate($attributes, array_merge($extra, [$column => $default])), function (Model $instance) use ($column, $step, $extra): void {
if (! $instance->wasRecentlyCreated) {
$instance->increment($column, $step, $extra); // @phpstan-ignore method.protected (handled by __call)
}
});
}

/**
* Insert new records or update the existing ones.
*/
Expand Down
Loading
Loading