diff --git a/.editorconfig b/.editorconfig
index fcdf61edc..80ce1de38 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,4 +6,7 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
-trim_trailing_whitespace = true
\ No newline at end of file
+trim_trailing_whitespace = true
+
+[*.yml]
+indent_size = 2
diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml
index 97b1e8a32..3664d752e 100644
--- a/.github/workflows/build-ci.yml
+++ b/.github/workflows/build-ci.yml
@@ -2,8 +2,6 @@ name: CI
on:
push:
- branches:
- tags:
pull_request:
jobs:
@@ -55,7 +53,7 @@ jobs:
- name: Show Docker version
run: if [[ "$DEBUG" == "true" ]]; then docker version && env; fi
env:
- DEBUG: ${{secrets.DEBUG}}
+ DEBUG: ${{ secrets.DEBUG }}
- name: Download Composer cache dependencies from cache
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
@@ -66,14 +64,8 @@ jobs:
key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ matrix.os }}-composer-
- name: Install dependencies
- run: |
- composer update --no-interaction $([[ "${{ matrix.mode }}" == low-deps ]] && echo ' --prefer-lowest --prefer-stable')
+ run: composer update --no-interaction $([[ "${{ matrix.mode }}" == low-deps ]] && echo ' --prefer-lowest --prefer-stable')
- name: Run tests
- run: |
- ./vendor/bin/phpunit --coverage-clover coverage.xml
+ run: ./vendor/bin/phpunit --coverage-clover coverage.xml
env:
MONGODB_URI: 'mongodb://127.0.0.1/?replicaSet=rs'
- - uses: codecov/codecov-action@v3
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- fail_ci_if_error: false
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
index e75ca3c53..c6f730d33 100644
--- a/.github/workflows/coding-standards.yml
+++ b/.github/workflows/coding-standards.yml
@@ -2,8 +2,6 @@ name: "Coding Standards"
on:
push:
- branches:
- tags:
pull_request:
env:
@@ -15,6 +13,11 @@ jobs:
name: "phpcs"
runs-on: "ubuntu-22.04"
+ permissions:
+ # Give the default GITHUB_TOKEN write permission to commit and push the
+ # added or changed files to the repository.
+ contents: write
+
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
@@ -50,6 +53,67 @@ jobs:
with:
composer-options: "--no-suggest"
+ - name: "Format the code"
+ continue-on-error: true
+ run: |
+ mkdir .cache
+ ./vendor/bin/phpcbf
+
# The -q option is required until phpcs v4 is released
- name: "Run PHP_CodeSniffer"
run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
+
+ - name: "Commit the changes"
+ uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: "apply phpcbf formatting"
+
+ analysis:
+ runs-on: "ubuntu-22.04"
+ continue-on-error: true
+ strategy:
+ matrix:
+ php:
+ - '8.1'
+ - '8.2'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: curl, mbstring
+ tools: composer:v2
+ coverage: none
+
+ - name: Cache dependencies
+ id: composer-cache
+ uses: actions/cache@v3
+ with:
+ path: ./vendor
+ key: composer-${{ hashFiles('**/composer.lock') }}
+
+ - name: Install dependencies
+ run: composer install
+
+ - name: Restore cache PHPStan results
+ id: phpstan-cache-restore
+ uses: actions/cache/restore@v3
+ with:
+ path: .cache
+ key: "phpstan-result-cache-${{ github.run_id }}"
+ restore-keys: |
+ phpstan-result-cache-
+
+ - name: Run PHPStan
+ run: ./vendor/bin/phpstan analyse --no-interaction --no-progress --ansi
+
+ - name: Save cache PHPStan results
+ id: phpstan-cache-save
+ if: always()
+ uses: actions/cache/save@v3
+ with:
+ path: .cache
+ key: ${{ steps.phpstan-cache-restore.outputs.cache-primary-key }}
diff --git a/.gitignore b/.gitignore
index d69c89d6f..80f343333 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,9 @@
*.sublime-workspace
.DS_Store
.idea/
-.phpunit.cache/
-.phpcs-cache
/vendor
composer.lock
composer.phar
phpunit.xml
+phpstan.neon
+/.cache/
diff --git a/composer.json b/composer.json
index 9f605c667..b04425751 100644
--- a/composer.json
+++ b/composer.json
@@ -35,7 +35,8 @@
"orchestra/testbench": "^8.0",
"mockery/mockery": "^1.4.4",
"doctrine/coding-standard": "12.0.x-dev",
- "spatie/laravel-query-builder": "^5.6"
+ "spatie/laravel-query-builder": "^5.6",
+ "phpstan/phpstan": "^1.10"
},
"replace": {
"jenssegers/mongodb": "self.version"
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 23bc44ab7..5f402d4ce 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -3,7 +3,7 @@
-
+
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 000000000..71a44a395
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,16 @@
+parameters:
+ ignoreErrors:
+ -
+ message: "#^Method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:push\\(\\) invoked with 3 parameters, 0 required\\.$#"
+ count: 3
+ path: src/Relations/BelongsToMany.php
+
+ -
+ message: "#^Method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:push\\(\\) invoked with 3 parameters, 0 required\\.$#"
+ count: 6
+ path: src/Relations/MorphToMany.php
+
+ -
+ message: "#^Method Illuminate\\\\Database\\\\Schema\\\\Blueprint\\:\\:create\\(\\) invoked with 1 parameter, 0 required\\.$#"
+ count: 1
+ path: src/Schema/Builder.php
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 000000000..518fe9ab8
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,16 @@
+includes:
+ - ./phpstan-baseline.neon
+
+parameters:
+ tmpDir: .cache/phpstan
+
+ paths:
+ - src
+
+ level: 2
+
+ editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
+
+ ignoreErrors:
+ - '#Unsafe usage of new static#'
+ - '#Call to an undefined method [a-zA-Z0-9\\_\<\>]+::[a-zA-Z]+\(\)#'
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 7a38678eb..8e5e9d3d6 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,15 +1,13 @@
-
+ cacheDirectory=".cache/phpunit"
+ executionOrder="depends,defects"
+ beStrictAboutCoverageMetadata="true"
+ beStrictAboutOutputDuringTests="true"
+ failOnRisky="true"
+ failOnWarning="true">
tests/
@@ -20,10 +18,15 @@
+
+
-
+
+
- ./src
+ ./src
diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php
index 948182ad3..b9005c442 100644
--- a/src/Eloquent/Builder.php
+++ b/src/Eloquent/Builder.php
@@ -16,6 +16,7 @@
use function is_array;
use function iterator_to_array;
+/** @method \MongoDB\Laravel\Query\Builder toBase() */
class Builder extends EloquentBuilder
{
use QueriesRelationships;
@@ -219,16 +220,15 @@ protected function ensureOrderForCursorPagination($shouldReverse = false)
}
if ($shouldReverse) {
- $this->query->orders = collect($this->query->orders)->map(function ($direction) {
- return $direction === 1 ? -1 : 1;
- })->toArray();
+ $this->query->orders = collect($this->query->orders)
+ ->map(static fn (int $direction) => $direction === 1 ? -1 : 1)
+ ->toArray();
}
- return collect($this->query->orders)->map(function ($direction, $column) {
- return [
+ return collect($this->query->orders)
+ ->map(static fn ($direction, $column) => [
'column' => $column,
'direction' => $direction === 1 ? 'asc' : 'desc',
- ];
- })->values();
+ ])->values();
}
}
diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php
index bbd45a32b..bcb672a3c 100644
--- a/src/Eloquent/Model.php
+++ b/src/Eloquent/Model.php
@@ -297,10 +297,10 @@ protected function asDecimal($value, $decimals)
public function fromJson($value, $asObject = false)
{
if (! is_string($value)) {
- $value = Json::encode($value ?? '');
+ $value = Json::encode($value);
}
- return Json::decode($value ?? '', ! $asObject);
+ return Json::decode($value, ! $asObject);
}
/** @inheritdoc */
diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php
index a1b028c9f..1d6b84ba8 100644
--- a/src/Relations/BelongsToMany.php
+++ b/src/Relations/BelongsToMany.php
@@ -15,8 +15,8 @@
use function array_map;
use function array_merge;
use function array_values;
+use function assert;
use function count;
-use function is_array;
use function is_numeric;
class BelongsToMany extends EloquentBelongsToMany
@@ -82,11 +82,11 @@ protected function setWhere()
}
/** @inheritdoc */
- public function save(Model $model, array $joining = [], $touch = true)
+ public function save(Model $model, array $pivotAttributes = [], $touch = true)
{
$model->save(['touch' => false]);
- $this->attach($model, $joining, $touch);
+ $this->attach($model, $pivotAttributes, $touch);
return $model;
}
@@ -126,12 +126,7 @@ public function sync($ids, $detaching = true)
// if they exist in the array of current ones, and if not we will insert.
$current = $this->parent->{$this->relatedPivotKey} ?: [];
- // See issue #256.
- if ($current instanceof Collection) {
- $current = $ids->modelKeys();
- }
-
- $records = $this->formatSyncList($ids);
+ $records = $this->formatRecordsList($ids);
$current = Arr::wrap($current);
@@ -171,6 +166,7 @@ public function sync($ids, $detaching = true)
public function updateExistingPivot($id, array $attributes, $touch = true)
{
// Do nothing, we have no pivot table.
+ return $this;
}
/** @inheritdoc */
@@ -229,6 +225,8 @@ public function detach($ids = [], $touch = true)
}
// Remove the relation to the parent.
+ assert($this->parent instanceof \MongoDB\Laravel\Eloquent\Model);
+ assert($query instanceof \MongoDB\Laravel\Eloquent\Builder);
$query->pull($this->foreignPivotKey, $this->parent->getKey());
if ($touch) {
@@ -266,7 +264,7 @@ public function newPivotQuery()
/**
* Create a new query builder for the related model.
*
- * @return \Illuminate\Database\Query\Builder
+ * @return Builder|Model
*/
public function newRelatedQuery()
{
@@ -295,28 +293,6 @@ public function getQualifiedRelatedPivotKeyName()
return $this->relatedPivotKey;
}
- /**
- * Format the sync list so that it is keyed by ID. (Legacy Support)
- * The original function has been renamed to formatRecordsList since Laravel 5.3.
- *
- * @deprecated
- *
- * @return array
- */
- protected function formatSyncList(array $records)
- {
- $results = [];
- foreach ($records as $id => $attributes) {
- if (! is_array($attributes)) {
- [$id, $attributes] = [$attributes, []];
- }
-
- $results[$id] = $attributes;
- }
-
- return $results;
- }
-
/**
* Get the name of the "where in" method for eager loading.
*
diff --git a/src/Relations/EmbedsMany.php b/src/Relations/EmbedsMany.php
index b97849f24..2d68af70b 100644
--- a/src/Relations/EmbedsMany.php
+++ b/src/Relations/EmbedsMany.php
@@ -9,6 +9,8 @@
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use MongoDB\BSON\ObjectID;
+use MongoDB\Driver\Exception\LogicException;
+use MongoDB\Laravel\Eloquent\Model as MongoDBModel;
use function array_key_exists;
use function array_values;
@@ -16,6 +18,7 @@
use function in_array;
use function is_array;
use function method_exists;
+use function throw_if;
class EmbedsMany extends EmbedsOneOrMany
{
@@ -82,7 +85,7 @@ public function performUpdate(Model $model)
// Get the correct foreign key value.
$foreignKey = $this->getForeignKeyValue($model);
- $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.$.');
+ $values = self::getUpdateValues($model->getDirty(), $this->localKey . '.$.');
// Update document in database.
$result = $this->toBase()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey)
@@ -195,10 +198,14 @@ public function destroy($ids = [])
/**
* Delete all embedded models.
*
- * @return int
+ * @param null $id
+ *
+ * @note The $id is not used to delete embedded models.
*/
- public function delete()
+ public function delete($id = null): int
{
+ throw_if($id !== null, new LogicException('The id parameter should not be used.'));
+
// Overwrite the local key with an empty array.
$result = $this->query->update([$this->localKey => []]);
@@ -224,9 +231,9 @@ public function detach($ids = [])
/**
* Save alias.
*
- * @return Model
+ * @return MongoDBModel
*/
- public function attach(Model $model)
+ public function attach(MongoDBModel $model)
{
return $this->save($model);
}
@@ -322,13 +329,13 @@ protected function getEmbedded()
}
/** @inheritdoc */
- protected function setEmbedded($models)
+ protected function setEmbedded($records)
{
- if (! is_array($models)) {
- $models = [$models];
+ if (! is_array($records)) {
+ $records = [$records];
}
- return parent::setEmbedded(array_values($models));
+ return parent::setEmbedded(array_values($records));
}
/** @inheritdoc */
diff --git a/src/Relations/EmbedsOne.php b/src/Relations/EmbedsOne.php
index 196415a55..678141cf1 100644
--- a/src/Relations/EmbedsOne.php
+++ b/src/Relations/EmbedsOne.php
@@ -6,6 +6,10 @@
use Illuminate\Database\Eloquent\Model;
use MongoDB\BSON\ObjectID;
+use MongoDB\Driver\Exception\LogicException;
+use Throwable;
+
+use function throw_if;
class EmbedsOne extends EmbedsOneOrMany
{
@@ -73,7 +77,7 @@ public function performUpdate(Model $model)
return $this->parent->save();
}
- $values = $this->getUpdateValues($model->getDirty(), $this->localKey . '.');
+ $values = self::getUpdateValues($model->getDirty(), $this->localKey . '.');
$result = $this->toBase()->update($values);
@@ -133,10 +137,16 @@ public function dissociate()
/**
* Delete all embedded models.
*
- * @return int
+ * @param ?string $id
+ *
+ * @throws LogicException|Throwable
+ *
+ * @note The $id is not used to delete embedded models.
*/
- public function delete()
+ public function delete($id = null): int
{
+ throw_if($id !== null, new LogicException('The id parameter should not be used.'));
+
return $this->performDelete();
}
diff --git a/src/Relations/EmbedsOneOrMany.php b/src/Relations/EmbedsOneOrMany.php
index 46f4f1e72..56fc62041 100644
--- a/src/Relations/EmbedsOneOrMany.php
+++ b/src/Relations/EmbedsOneOrMany.php
@@ -8,11 +8,15 @@
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\Relation;
+use Illuminate\Database\Query\Expression;
+use MongoDB\Driver\Exception\LogicException;
use MongoDB\Laravel\Eloquent\Model;
+use Throwable;
use function array_merge;
use function count;
use function is_array;
+use function throw_if;
abstract class EmbedsOneOrMany extends Relation
{
@@ -42,8 +46,8 @@ abstract class EmbedsOneOrMany extends Relation
*/
public function __construct(Builder $query, Model $parent, Model $related, string $localKey, string $foreignKey, string $relation)
{
- $this->query = $query;
- $this->parent = $parent;
+ parent::__construct($query, $parent);
+
$this->related = $related;
$this->localKey = $localKey;
$this->foreignKey = $foreignKey;
@@ -54,8 +58,6 @@ public function __construct(Builder $query, Model $parent, Model $related, strin
if ($parentRelation) {
$this->query = $parentRelation->getQuery();
}
-
- $this->addConstraints();
}
/** @inheritdoc */
@@ -101,10 +103,16 @@ public function get($columns = ['*'])
/**
* Get the number of embedded models.
*
- * @return int
+ * @param Expression|string $columns
+ *
+ * @throws LogicException|Throwable
+ *
+ * @note The $column parameter is not used to count embedded models.
*/
- public function count()
+ public function count($columns = '*'): int
{
+ throw_if($columns !== '*', new LogicException('The columns parameter should not be used.'));
+
return count($this->getEmbedded());
}
@@ -261,21 +269,21 @@ protected function toCollection(array $records = [])
/**
* Create a related model instanced.
*
- * @param array $attributes
+ * @param mixed $attributes
*
- * @return Model
+ * @return Model | null
*/
- protected function toModel($attributes = [])
+ protected function toModel(mixed $attributes = []): Model|null
{
if ($attributes === null) {
- return;
+ return null;
}
$connection = $this->related->getConnection();
$model = $this->related->newFromBuilder(
(array) $attributes,
- $connection ? $connection->getName() : null,
+ $connection?->getName(),
);
$model->setParentRelation($this);
@@ -394,8 +402,8 @@ public function getQualifiedForeignKeyName()
/**
* Get the name of the "where in" method for eager loading.
*
- * @param \Illuminate\Database\Eloquent\Model $model
- * @param string $key
+ * @param EloquentModel $model
+ * @param string $key
*
* @return string
*/
diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php
index 9c9576d90..a2c55969f 100644
--- a/src/Relations/MorphToMany.php
+++ b/src/Relations/MorphToMany.php
@@ -85,11 +85,11 @@ protected function setWhere()
}
/** @inheritdoc */
- public function save(Model $model, array $joining = [], $touch = true)
+ public function save(Model $model, array $pivotAttributes = [], $touch = true)
{
$model->save(['touch' => false]);
- $this->attach($model, $joining, $touch);
+ $this->attach($model, $pivotAttributes, $touch);
return $model;
}
@@ -133,11 +133,6 @@ public function sync($ids, $detaching = true)
$current = $this->parent->{$this->relatedPivotKey} ?: [];
}
- // See issue #256.
- if ($current instanceof Collection) {
- $current = $this->parseIds($current);
- }
-
$records = $this->formatRecordsList($ids);
$current = Arr::wrap($current);
@@ -175,7 +170,7 @@ public function sync($ids, $detaching = true)
}
/** @inheritdoc */
- public function updateExistingPivot($id, array $attributes, $touch = true)
+ public function updateExistingPivot($id, array $attributes, $touch = true): void
{
// Do nothing, we have no pivot table.
}
@@ -272,12 +267,13 @@ public function detach($ids = [], $touch = true)
// Remove the relation from the parent.
$data = [];
foreach ($ids as $item) {
- $data = array_merge($data, [
+ $data = [
+ ...$data,
[
$this->relatedPivotKey => $item,
- $this->morphType => $this->related->getMorphClass(),
+ $this->morphType => $this->related->getMorphClass(),
],
- ]);
+ ];
}
$this->parent->pull($this->table, $data);
@@ -378,8 +374,8 @@ protected function whereInMethod(Model $model, $key)
/**
* Extract ids from given pivot table data
*
- * @param array $data
- * @param string|null $relatedPivotKey
+ * @param array $data
+ * @param string|null $relatedPivotKey
*
* @return mixed
*/
diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php
index 6dd28d3b2..52a5762f5 100644
--- a/src/Schema/Blueprint.php
+++ b/src/Schema/Blueprint.php
@@ -44,6 +44,8 @@ class Blueprint extends SchemaBlueprint
*/
public function __construct(Connection $connection, string $collection)
{
+ parent::__construct($collection);
+
$this->connection = $connection;
$this->collection = $this->connection->getCollection($collection);
@@ -82,11 +84,11 @@ public function primary($columns = null, $name = null, $algorithm = null, $optio
}
/** @inheritdoc */
- public function dropIndex($indexOrColumns = null)
+ public function dropIndex($index = null)
{
- $indexOrColumns = $this->transformColumns($indexOrColumns);
+ $index = $this->transformColumns($index);
- $this->collection->dropIndex($indexOrColumns);
+ $this->collection->dropIndex($index);
return $this;
}
@@ -275,6 +277,8 @@ public function create($options = [])
public function drop()
{
$this->collection->drop();
+
+ return $this;
}
/** @inheritdoc */
@@ -339,11 +343,11 @@ protected function fluent($columns = null)
* Allows the use of unsupported schema methods.
*
* @param string $method
- * @param array $args
+ * @param array $parameters
*
* @return Blueprint
*/
- public function __call($method, $args)
+ public function __call($method, $parameters)
{
// Dummy.
return $this;
diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php
index af311df6c..bfa0e4715 100644
--- a/src/Schema/Builder.php
+++ b/src/Schema/Builder.php
@@ -44,9 +44,9 @@ public function hasCollection($name)
}
/** @inheritdoc */
- public function hasTable($collection)
+ public function hasTable($table)
{
- return $this->hasCollection($collection);
+ return $this->hasCollection($table);
}
/**
@@ -66,15 +66,15 @@ public function collection($collection, Closure $callback)
}
/** @inheritdoc */
- public function table($collection, Closure $callback)
+ public function table($table, Closure $callback)
{
- $this->collection($collection, $callback);
+ $this->collection($table, $callback);
}
/** @inheritdoc */
- public function create($collection, ?Closure $callback = null, array $options = [])
+ public function create($table, ?Closure $callback = null, array $options = [])
{
- $blueprint = $this->createBlueprint($collection);
+ $blueprint = $this->createBlueprint($table);
$blueprint->create($options);
@@ -84,17 +84,17 @@ public function create($collection, ?Closure $callback = null, array $options =
}
/** @inheritdoc */
- public function dropIfExists($collection)
+ public function dropIfExists($table)
{
- if ($this->hasCollection($collection)) {
- $this->drop($collection);
+ if ($this->hasCollection($table)) {
+ $this->drop($table);
}
}
/** @inheritdoc */
- public function drop($collection)
+ public function drop($table)
{
- $blueprint = $this->createBlueprint($collection);
+ $blueprint = $this->createBlueprint($table);
$blueprint->drop();
}
@@ -108,9 +108,9 @@ public function dropAllTables()
}
/** @inheritdoc */
- protected function createBlueprint($collection, ?Closure $callback = null)
+ protected function createBlueprint($table, ?Closure $callback = null)
{
- return new Blueprint($this->connection, $collection);
+ return new Blueprint($this->connection, $table);
}
/**