Skip to content

Commit

Permalink
Backport never released features: cascade persisting of belongs* rela…
Browse files Browse the repository at this point in the history
…tions and builder macros
  • Loading branch information
patrickbrouwers committed Jan 18, 2024
1 parent 4471416 commit ff5cbf6
Show file tree
Hide file tree
Showing 19 changed files with 763 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .phpunit.cache/test-results

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/Concerns/PersistRelations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Maatwebsite\Excel\Concerns;

interface PersistRelations
{

}
17 changes: 13 additions & 4 deletions src/ExcelServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Maatwebsite\Excel;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
use Laravel\Lumen\Application as LumenApplication;
Expand All @@ -10,8 +11,12 @@
use Maatwebsite\Excel\Console\ImportMakeCommand;
use Maatwebsite\Excel\Files\Filesystem;
use Maatwebsite\Excel\Files\TemporaryFileFactory;
use Maatwebsite\Excel\Mixins\DownloadCollection;
use Maatwebsite\Excel\Mixins\StoreCollection;
use Maatwebsite\Excel\Mixins\DownloadCollectionMixin;
use Maatwebsite\Excel\Mixins\DownloadQueryMacro;
use Maatwebsite\Excel\Mixins\ImportAsMacro;
use Maatwebsite\Excel\Mixins\ImportMacro;
use Maatwebsite\Excel\Mixins\StoreCollectionMixin;
use Maatwebsite\Excel\Mixins\StoreQueryMacro;
use Maatwebsite\Excel\Transactions\TransactionHandler;
use Maatwebsite\Excel\Transactions\TransactionManager;

Expand Down Expand Up @@ -98,8 +103,12 @@ public function register()
$this->app->alias('excel', Exporter::class);
$this->app->alias('excel', Importer::class);

Collection::mixin(new DownloadCollection);
Collection::mixin(new StoreCollection);
Collection::mixin(new DownloadCollectionMixin);
Collection::mixin(new StoreCollectionMixin);
Builder::macro('downloadExcel', (new DownloadQueryMacro)());
Builder::macro('storeExcel', (new StoreQueryMacro())());
Builder::macro('import', (new ImportMacro())());
Builder::macro('importAs', (new ImportAsMacro())());

$this->commands([
ExportMakeCommand::class,
Expand Down
16 changes: 14 additions & 2 deletions src/Imports/ModelManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\PersistRelations;
use Maatwebsite\Excel\Concerns\SkipsOnError;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithUpsertColumns;
use Maatwebsite\Excel\Concerns\WithUpserts;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Exceptions\RowSkippedException;
use Maatwebsite\Excel\Imports\Persistence\CascadePersistManager;
use Maatwebsite\Excel\Validators\RowValidator;
use Maatwebsite\Excel\Validators\ValidationException;
use Throwable;
Expand All @@ -30,12 +32,18 @@ class ModelManager
*/
private $remembersRowNumber = false;

/**
* @var CascadePersistManager
*/
private $cascade;

/**
* @param RowValidator $validator
*/
public function __construct(RowValidator $validator)
public function __construct(RowValidator $validator, CascadePersistManager $cascade)
{
$this->validator = $validator;
$this->cascade = $cascade;
}

/**
Expand Down Expand Up @@ -144,7 +152,11 @@ private function singleFlush(ToModel $import)
return;
}

$model->saveOrFail();
if ($import instanceof PersistRelations) {
$this->cascade->persist($model);
} else{
$model->saveOrFail();
}
} catch (Throwable $e) {
$this->handleException($import, $e);
}
Expand Down
119 changes: 119 additions & 0 deletions src/Imports/Persistence/CascadePersistManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Maatwebsite\Excel\Imports\Persistence;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Transactions\TransactionHandler;

/** @todo */
class CascadePersistManager
{
/**
* @var TransactionHandler
*/
private $transaction;

/**
* @param TransactionHandler $transaction
*/
public function __construct(TransactionHandler $transaction)
{
$this->transaction = $transaction;
}

/**
* @param Model $model
*
* @return bool
*/
public function persist(Model $model): bool
{
return ($this->transaction)(function () use ($model) {
return $this->save($model);
});
}

/**
* @param Model $model
*
* @return bool
*/
private function save(Model $model): bool
{
if (!$model->save()) {
return false;
}

foreach ($model->getRelations() as $relationName => $models) {
$models = array_filter(
$models instanceof Collection ? $models->all() : [$models]
);

$relation = $model->{$relationName}();

if ($relation instanceof BelongsTo) {
if (!$this->persistBelongsTo($relation, $models)) {
return false;
}
}

if ($relation instanceof BelongsToMany) {
if (!$this->persistBelongsToMany($relation, $models)) {
return false;
}
}
}

// We need to save the model again to
// make sure all updates are performed.
$model->save();

return true;
}

/**
* @param BelongsTo $relation
* @param array $models
*
* @return bool
*/
private function persistBelongsTo(BelongsTo $relation, array $models): bool
{
// With belongs to, we first need to save all relations,
// so we can use their foreign key to attach to the relation.
foreach ($models as $model) {

// Cascade any relations that this child model may have.
if (!$this->save($model)) {
return false;
}

$relation->associate($model);
}

return true;
}

/**
* @param BelongsToMany $relation
* @param array $models
*
* @return bool
*/
private function persistBelongsToMany(BelongsToMany $relation, array $models): bool
{
foreach ($models as $model) {
$relation->save($model);

// Cascade any relations that this child model may have.
if (!$this->save($model)) {
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Sheet;

class DownloadCollection
class DownloadCollectionMixin
{
/**
* @return callable
Expand Down
69 changes: 69 additions & 0 deletions src/Mixins/DownloadQueryMacro.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Maatwebsite\Excel\Mixins;

use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Sheet;

class DownloadQueryMacro
{
public function __invoke()
{
return function (string $fileName, string $writerType = null, $withHeadings = false) {
$export = new class($this, $withHeadings) implements FromQuery, WithHeadings {
use Exportable;

/**
* @var bool
*/
private $withHeadings;

/**
* @var Builder
*/
private $query;

/**
* @param $query
* @param bool $withHeadings
*/
public function __construct($query, bool $withHeadings = false)
{
$this->query = $query;
$this->withHeadings = $withHeadings;
}

/**
* @return Builder
*/
public function query()
{
return $this->query;
}

/**
* @return array
*/
public function headings(): array
{
if (!$this->withHeadings) {
return [];
}

$firstRow = (clone $this->query)->first();

if ($firstRow) {
return array_keys(Sheet::mapArraybleRow($firstRow));
}

return [];
}
};

return $export->download($fileName, $writerType);
};
}
}
53 changes: 53 additions & 0 deletions src/Mixins/ImportAsMacro.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Maatwebsite\Excel\Mixins;

use Illuminate\Database\Eloquent\Model;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\ToModel;

class ImportAsMacro
{
public function __invoke()
{
return function (string $filename, callable $mapping, string $disk = null, string $readerType = null) {
$import = new class(get_class($this->getModel()), $mapping) implements ToModel {
use Importable;

/**
* @var string
*/
private $model;

/**
* @var callable
*/
private $mapping;

/**
* @param string $model
* @param callable $mapping
*/
public function __construct(string $model, callable $mapping)
{
$this->model = $model;
$this->mapping = $mapping;
}

/**
* @param array $row
*
* @return Model|Model[]|null
*/
public function model(array $row)
{
return (new $this->model)->fill(
($this->mapping)($row)
);
}
};

return $import->import($filename, $disk, $readerType);
};
}
}
45 changes: 45 additions & 0 deletions src/Mixins/ImportMacro.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Maatwebsite\Excel\Mixins;

use Illuminate\Database\Eloquent\Model;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;

class ImportMacro
{
public function __invoke()
{
return function (string $filename, string $disk = null, string $readerType = null) {
$import = new class(get_class($this->getModel())) implements ToModel, WithHeadingRow {
use Importable;

/**
* @var string
*/
private $model;

/**
* @param string $model
*/
public function __construct(string $model)
{
$this->model = $model;
}

/**
* @param array $row
*
* @return Model|Model[]|null
*/
public function model(array $row)
{
return (new $this->model)->fill($row);
}
};

return $import->import($filename, $disk, $readerType);
};
}
}
Loading

0 comments on commit ff5cbf6

Please sign in to comment.