Skip to content

Comments

549 remove SpoonCSV#428

Open
tijsverkoyen wants to merge 6 commits intomasterfrom
549-remove-spooncsv
Open

549 remove SpoonCSV#428
tijsverkoyen wants to merge 6 commits intomasterfrom
549-remove-spooncsv

Conversation

@tijsverkoyen
Copy link
Member

@tijsverkoyen tijsverkoyen commented Feb 25, 2026

See https://next-app.activecollab.com/108877/projects/5029?modal=Task-243463-62

Summary by Sourcery

Remove legacy CSV import code and centralize CSV handling on PhpSpreadsheet-based utilities.

Enhancements:

  • Drop deprecated profile CSV import method in favor of array-based import.
  • Refactor CSV reader to support user-specific delimiter and line-ending options and to expose a reusable file-to-array converter.
  • Update profiles import action to use the shared CSV reader utility instead of local CSV parsing logic.
  • Simplify CSV writer to construct PhpSpreadsheet Csv writers directly and reuse generic response generation for user-specific downloads.
  • Clarify documentation/comments for profiles import and export template actions.

Build:

  • Upgrade phpoffice/phpspreadsheet dependency to version ^5.4.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 25, 2026

Reviewer's Guide

Replaces legacy CSV import/export infrastructure (including deprecated profile CSV import and SpoonCSV-based reader/writer usage) with PhpSpreadsheet-native CSV handling, centralizes CSV reading logic in ForkCMS\Utility\Csv\Reader, and upgrades phpoffice/phpspreadsheet to v5.4 while keeping user-specific CSV settings behavior intact.

Sequence diagram for profiles CSV import using new Reader

sequenceDiagram
    actor AdminUser
    participant Browser
    participant ImportAction as Backend_Modules_Profiles_Actions_Import
    participant Auth as Backend_Core_Engine_Authentication
    participant User as Backend_Core_Engine_User
    participant CsvReader as ForkCMS_Utility_Csv_Reader
    participant ProfilesModel as Backend_Modules_Profiles_Engine_Model

    AdminUser ->> Browser: Upload CSV and submit import form
    Browser ->> ImportAction: HTTP POST /profiles/import
    ImportAction ->> ImportAction: validateForm()

    ImportAction ->> Auth: getUser()
    Auth -->> ImportAction: Backend_Core_Engine_User
    ImportAction ->> CsvReader: findColumnIndexes(path, [email,display_name,password], user)
    CsvReader -->> ImportAction: columnIndexes

    ImportAction ->> ImportAction: validate required columns

    ImportAction ->> Auth: getUser()
    Auth -->> ImportAction: Backend_Core_Engine_User
    ImportAction ->> CsvReader: convertFileToArray(path, mapping, user)
    CsvReader -->> ImportAction: csvData array

    ImportAction ->> ProfilesModel: importFromArray(csvData, groupId, overwriteExisting)
    ProfilesModel -->> ImportAction: statistics

    ImportAction ->> Browser: HTTP redirect to profiles index with report
    Browser -->> AdminUser: Shows import result message
Loading

Class diagram for updated CSV utilities and profiles import flow

classDiagram
    class ForkCMS_Utility_Csv_Reader {
        +findColumnIndexes(path string, columns array, user Backend_Core_Engine_User) array
        +convertFileToArray(path string, mapping array, user Backend_Core_Engine_User) array
        +convertRowIntoMappedArray(row PhpOffice_PhpSpreadsheet_Worksheet_Row, mapping array) array
        -getUserOptions(user Backend_Core_Engine_User) array
        -getReader(options array) PhpOffice_PhpSpreadsheet_Reader_Csv
    }

    class ForkCMS_Utility_Csv_Writer {
        <<readonly>>
        -charset string
        +__construct(charset string)
        +getResponse(spreadsheet PhpOffice_PhpSpreadsheet_Spreadsheet, filename string, options array) Symfony_Component_HttpFoundation_StreamedResponse
        +getResponseForUser(spreadsheet PhpOffice_PhpSpreadsheet_Spreadsheet, filename string, user Backend_Core_Engine_User) Symfony_Component_HttpFoundation_StreamedResponse
        -getDefaultOptions() array
        -getUserOptions(user Backend_Core_Engine_User) array
        -getWriter(spreadsheet PhpOffice_PhpSpreadsheet_Spreadsheet, options array) PhpOffice_PhpSpreadsheet_Writer_Csv
        -getStreamedResponse(writer PhpOffice_PhpSpreadsheet_Writer_Csv, filename string) Symfony_Component_HttpFoundation_StreamedResponse
    }

    class Backend_Modules_Profiles_Actions_Import {
        -form Backend_Core_Engine_Form
        +execute() void
        -validateForm() void
    }

    class Backend_Modules_Profiles_Engine_Model {
        +importFromArray(data array, groupId int, overwriteExisting bool) array
    }

    class Backend_Core_Engine_Authentication {
        +getUser() Backend_Core_Engine_User
    }

    class Backend_Core_Engine_User {
        +getSetting(name string) mixed
    }

    class PhpOffice_PhpSpreadsheet_Spreadsheet
    class PhpOffice_PhpSpreadsheet_Writer_Csv
    class PhpOffice_PhpSpreadsheet_Reader_Csv
    class PhpOffice_PhpSpreadsheet_Worksheet_Row
    class Symfony_Component_HttpFoundation_StreamedResponse

    Backend_Modules_Profiles_Actions_Import --> ForkCMS_Utility_Csv_Reader : uses
    Backend_Modules_Profiles_Actions_Import --> Backend_Modules_Profiles_Engine_Model : imports_profiles_via
    Backend_Modules_Profiles_Actions_Import --> Backend_Core_Engine_Authentication : gets_current_user

    ForkCMS_Utility_Csv_Reader --> Backend_Core_Engine_User : reads_csv_settings
    ForkCMS_Utility_Csv_Reader --> PhpOffice_PhpSpreadsheet_Reader_Csv : configures_reader

    ForkCMS_Utility_Csv_Writer --> Backend_Core_Engine_User : reads_csv_settings
    ForkCMS_Utility_Csv_Writer --> PhpOffice_PhpSpreadsheet_Spreadsheet : writes_from
    ForkCMS_Utility_Csv_Writer --> PhpOffice_PhpSpreadsheet_Writer_Csv : configures_writer
    ForkCMS_Utility_Csv_Writer --> Symfony_Component_HttpFoundation_StreamedResponse : returns

    Backend_Core_Engine_Authentication --> Backend_Core_Engine_User : returns
Loading

File-Level Changes

Change Details Files
Remove deprecated, SpoonCSV-based profile CSV import API and rely on array-based import instead.
  • Delete Profiles\Engine\Model::importCsv deprecated method that imported profiles directly from CSV data arrays.
  • Ensure Profiles CSV import flow uses BackendProfilesModel::importFromArray as the sole import entry point.
src/Backend/Modules/Profiles/Engine/Model.php
Centralize CSV reading and add support for user-specific CSV options using native PhpSpreadsheet Csv reader.
  • Introduce Reader::getUserOptions(User) to derive delimiter and line-ending from backend user settings.
  • Introduce Reader::getReader(array $options) that constructs and configures a PhpOffice\PhpSpreadsheet\Reader\Csv instance without IOFactory.
  • Extend Reader::findColumnIndexes to accept a User argument and use the configured Csv reader with ChunkReadFilter.
  • Add Reader::convertFileToArray to load CSV files into arrays based on a column index mapping and ColumnsFilter, skipping the header row.
src/ForkCMS/Utility/Csv/Reader.php
Refactor Profiles import action to use the shared CSV Reader utilities with user-aware configuration.
  • Inject current authenticated Backend user into Reader::findColumnIndexes and Reader::convertFileToArray via Authentication::getUser().
  • Replace local Import::convertFileToArray helper with Reader::convertFileToArray and remove the duplicated IOFactory/ColumnsFilter logic.
  • Clarify Import action PHPDoc to describe CSV profile import instead of generic add action wording.
src/Backend/Modules/Profiles/Actions/Import.php
Simplify CSV writer to use PhpSpreadsheet Csv writer directly and align writer options handling.
  • Change Writer to be a readonly class and construct PhpOffice\PhpSpreadsheet\Writer\Csv directly instead of via IOFactory.
  • Apply writer options by calling setter methods directly on the Csv writer instance instead of using call_user_func.
  • Have Writer::getResponseForUser delegate to getResponse using only user-specific CSV options, which already merges defaults.
src/ForkCMS/Utility/Csv/Writer.php
Minor documentation update for template export action.
  • Adjust ExportTemplate action PHPDoc to describe exporting a CSV template for import.
src/Backend/Modules/Profiles/Actions/ExportTemplate.php
Upgrade PhpSpreadsheet dependency and remove legacy backend CSV helper class tied to old SpoonCSV approach.
  • Bump phpoffice/phpspreadsheet dependency from ^1.12 to ^5.4 in composer.json to support native Csv reader/writer usage.
  • Remove src/Backend/Core/Engine/Csv.php which provided legacy CSV functionality that is no longer used with the new Reader/Writer utilities.
composer.json
src/Backend/Core/Engine/Csv.php

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Reader::getUserOptions assumes csv_split_character and csv_line_ending are always set on the user; consider adding sane defaults or guards so the new CSV reader behavior doesn’t break when these settings are missing or misconfigured.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Reader::getUserOptions assumes `csv_split_character` and `csv_line_ending` are always set on the user; consider adding sane defaults or guards so the new CSV reader behavior doesn’t break when these settings are missing or misconfigured.

## Individual Comments

### Comment 1
<location path="src/ForkCMS/Utility/Csv/Reader.php" line_range="13-26" />
<code_context>
+    private function getUserOptions(User $user): array
     {
-        $reader = IOFactory::createReader('Csv');
+        $options['Delimiter'] = $user->getSetting('csv_split_character');
+
+        $lineEnding = $user->getSetting('csv_line_ending');
+        if ($lineEnding === '\n') {
+            $options['LineEnding'] = "\n";
+        }
+        if ($lineEnding === '\r\n') {
+            $options['LineEnding'] = "\r\n";
+        }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Guard against null/empty CSV settings so defaults from PhpSpreadsheet still apply when user settings are not configured.

As written, `csv_split_character` / `csv_line_ending` will always populate `$options`, even when unset or empty, overriding the Csv reader’s defaults with null/empty values. Please only set `Delimiter` and `LineEnding` when the retrieved setting is non-empty so the reader can fall back to its built-in defaults otherwise.

```suggestion
    private function getUserOptions(User $user): array
    {
        $options = [];

        $delimiter = $user->getSetting('csv_split_character');
        if (!empty($delimiter)) {
            $options['Delimiter'] = $delimiter;
        }

        $lineEnding = $user->getSetting('csv_line_ending');
        if (!empty($lineEnding)) {
            if ($lineEnding === '\n') {
                $options['LineEnding'] = "\n";
            } elseif ($lineEnding === '\r\n') {
                $options['LineEnding'] = "\r\n";
            }
        }

        return $options;
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request removes the deprecated SpoonCSV dependency by replacing it with direct usage of PhpOffice/PhpSpreadsheet for CSV reading and writing operations. The changes modernize the CSV handling infrastructure by removing the legacy Backend\Core\Engine\Csv class that extended SpoonFileCSV and consolidating CSV operations in the ForkCMS\Utility\Csv namespace.

Changes:

  • Removed deprecated Backend\Core\Engine\Csv class and BackendProfilesModel::importCsv() method
  • Refactored Writer and Reader classes to use PhpSpreadsheet directly with user-specific CSV options
  • Updated Import action to use the refactored Reader service for both column detection and file conversion
  • Updated phpoffice/phpspreadsheet version constraint in composer.json

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
composer.json Updates phpoffice/phpspreadsheet version constraint
src/ForkCMS/Utility/Csv/Writer.php Converts to readonly class, removes unused imports, replaces IOFactory with direct instantiation, simplifies method call syntax
src/ForkCMS/Utility/Csv/Reader.php Adds getUserOptions and getReader methods, adds new convertFileToArray method, updates findColumnIndexes to accept User parameter
src/Backend/Modules/Profiles/Engine/Model.php Removes deprecated importCsv method
src/Backend/Modules/Profiles/Actions/Import.php Updates to use Reader service for file conversion, removes local convertFileToArray method, corrects documentation
src/Backend/Modules/Profiles/Actions/ExportTemplate.php Updates documentation comment
src/Backend/Core/Engine/Csv.php Completely removes deprecated class file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


/**
* This is the add-action, it will display a form to add a new profile.
* This is the import-action, it will display to import a CSV file with profiles to create.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The documentation comment has a grammatical error. "display to import" should be "display a form to import" for proper English grammar.

Suggested change
* This is the import-action, it will display to import a CSV file with profiles to create.
* This is the import-action, it will display a form to import a CSV file with profiles to create.

Copilot uses AI. Check for mistakes.
readonly class Writer
{
public function __construct(private readonly string $charset)
public function __construct(private string $charset)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

In a readonly class, all instance properties must be declared as readonly. The property $charset should be declared as 'private readonly string $charset' instead of 'private string $charset'. This is a PHP language requirement for readonly classes.

Suggested change
public function __construct(private string $charset)
public function __construct(private readonly string $charset)

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +25
private function getUserOptions(User $user): array
{
$reader = IOFactory::createReader('Csv');
$options['Delimiter'] = $user->getSetting('csv_split_character');

$lineEnding = $user->getSetting('csv_line_ending');
if ($lineEnding === '\n') {
$options['LineEnding'] = "\n";
}
if ($lineEnding === '\r\n') {
$options['LineEnding'] = "\r\n";
}

return $options;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The $options array is used without being initialized. If neither line ending condition is met (lines 18-23), the method would return an undefined variable. Initialize $options as an empty array at the beginning of the method: $options = [];

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant