# Upgrading from 6.x to 7.x

Due to an extensive refactoring aiming to improve DX, there is many breaking changes in the API... But stay with me, it's mainly function renaming and new arguments.

# Laravel 8+ and php 8.0+ required

It's not a BC, but still, minimal requirements are now these.

# Type hinting everywhere (part II)

In order to reinforce the API, we decided to use PHP type hinting everywhere. Sharp 6 handled the big part, but with php 8.0 we could add a few that were missing.

# EntityListQueryParams is now an instance property of an EntityList

This means that the getListData() function no longer has a $params argument, which must be replaced by $this->queryParams. This change makes it much easier to build the EntityList depending on the request (hide some columns for instance).

# DashboardQueryParams is now an instance property of a Dashboard

Similarly, the buildWidgetsData() function no longer has a $params argument, which must be replaced by $this->queryParams.

# All configuration methods have now a name prefixed with configure...

The motivation for this was to provide a clear list of available methods for configuration to the developer. This could lead to many changes, but it's quite easy to fix them. Here's the full list of renamed methods:

Renamed methods of SharpEntityList:

  • setInstanceIdAttribute() -> configureInstanceIdAttribute()
  • setSearchable() -> configureSearchable()
  • setDefaultSort() -> configureDefaultSort()
  • setPaginated() -> configurePaginated()
  • setReorderable() -> configureReorderable()
  • setPrimaryEntityCommand() -> configurePrimaryEntityCommand()
  • setMultiformAttribute() -> configureMultiformAttribute()

Renamed methods of SharpForm:

  • setDisplayShowPageAfterCreation() -> configureDisplayShowPageAfterCreation()
  • setDisplayShowPageAfterCreation() -> configureBreadcrumbCustomLabelAttribute()

Renamed methods of SharpShow:

  • setMultiformAttribute() -> configureMultiformAttribute()
  • setEntityState() -> configureEntityState()
  • setBreadcrumbCustomLabelAttribute() -> configureBreadcrumbCustomLabelAttribute()

Renamed methods of Command:

  • setConfirmationText() -> configureConfirmationText()
  • setDescription() -> configureDescription()
  • setFormModalTitle() -> configureFormModalTitle()

# Add proxy objects for EntityList columns and layout

The idea is to provide dedicated objects when needed to clearly indicate the API to the developer.

# buildListDataContainers() is renamed to buildListFields(EntityListFieldsContainer $fieldsContainer)

The $fieldsContainer parameter is a proxy object with the needed ->addField(...) function. As a bonus, EntityListDataContainer was renamed EntityListField for clarity.

Example: you need to refactor you code from this:

function buildListDataContainers(): void
{
    $this
        ->addDataContainer(
            EntityListDataContainer::make("name")
                ->setLabel("Name")
        )
        ->addDataContainer(
            EntityListDataContainer::make("age")
                ->setLabel("Age")
        );
}

To this:

function buildListFields(EntityListFieldsContainer $fieldsContainer): void
{
    $fieldsContainer
        ->addField(
            EntityListField::make("name")
                ->setLabel("Name")
        )
        ->addField(
            EntityListField::make("age")
                ->setLabel("Age")
        );
}

# buildListLayout() signature has changed to buildListLayout(EntityListFieldsLayout $fieldsLayout)

The $fieldsLayout parameter is a proxy object with the needed ->addColumn(...) function.

Example: you need to refactor you code from this:

function buildListLayout(): void
{
    $this->addColumn("picture", 1)
        ->addColumn("name", 2)
        ->addColumn("capacity", 2)
        ->addColumn("type:label", 2)
        ->addColumn("pilots")
        ->addColumn("messages_sent_count");
}

To this:

function buildListLayout(EntityListFieldsLayout $fieldsLayout): void
{
    $fieldsLayout->addColumn("picture", 1)
        ->addColumn("name", 2)
        ->addColumn("capacity", 2)
        ->addColumn("type:label", 2)
        ->addColumn("pilots")
        ->addColumn("messages_sent_count");
}

# EntityList's ->addColumn() no longer has optional parameter for small screens

Instead, a new optional buildListLayoutForSmallScreens() is available. Refer to doc for details.

# Add proxy objects for Form fields and layout

# buildFormFields() is renamed to buildFormFields(FieldsContainer $formFields)

The $formFields parameter is a proxy object with the needed ->addField(...) function.

Example: you need to refactor you code from this:

function buildFormFields(): void
{
    $this
        ->addField(
            SharpFormTextField::make("name")
                ->setLabel("Name")
        );
}

To this:

function buildFormFields(FieldsContainer $formFields): void
{
    $formFields
        ->addField(
            SharpFormTextField::make("name")
                ->setLabel("Name")
        );
}

# buildFormLayout() signature has changed to buildFormLayout(FormLayout $formLayout)

The $formLayout parameter is a proxy object with the needed ->addTab(...) and ->addColumn(...) functions.

Example: you need to refactor you code from this:

function buildFormLayout(): void
{
    $this
        ->addColumn(6, function(FormLayoutColumn $column) {
            return $column->withSingleField("name")
                ->withSingleField("email");
        });
}

To this:

function buildFormLayout(FormLayout $formLayout): void
{
    $formLayout
        ->addColumn(6, function(FormLayoutColumn $column) {
            return $column->withSingleField("name")
                ->withSingleField("email");
        });
}

# Add proxy objects for Show fields and layout

# buildShowFields() is renamed to buildShowFields(FieldsContainer $showFields)

The $showFields parameter is a proxy object with the needed ->addField(...) function.

Example: you need to refactor you code from this:

function buildShowFields(): void
{
    $this
        ->addField(
            SharpShowTextField::make("name")
                ->setLabel("Name")
        );
}

To this:

function buildShowFields(FieldsContainer $showFields): void
{
    $showFields
        ->addField(
            SharpShowTextField::make("name")
                ->setLabel("Name")
        );
}

# buildShowLayout() signature has changed to buildShowLayout(ShowLayout $showLayout)

The $showLayout parameter is a proxy object with the needed ->addSection(...) and ->addEntityListSection(...) functions.

Example: you need to refactor you code from this:

function buildShowLayout(): void
{
    $this
        ->addSection("Identity", function(ShowLayoutSection $section) {
            $section
                ->addColumn(6, function(ShowLayoutColumn $column) {
                    $column->withSingleField("name");
                });
        });
}

To this:

function buildShowLayout(ShowLayout $showLayout): void
{
    $showLayout
        ->addSection("Identity", function(ShowLayoutSection $section) {
            $section
                ->addColumn(6, function(ShowLayoutColumn $column) {
                    $column->withSingleField("name");
                });
        });
}

# Add proxy objects for Dashboard widgets and layout

# buildWidgets() is renamed to buildWidgets(WidgetsContainer $widgetsContainer)

The $widgetsContainer parameter is a proxy object with the needed ->addWidget(...) function.

Example: you need to refactor you code from this:

function buildWidgets(): void
{
    $this
        ->addWidget(
            SharpBarGraphWidget::make("travels")
        );
}

To this:

function buildWidgets(WidgetsContainer $widgetsContainer): void
{
    $widgetsContainer
        ->addWidget(
            SharpBarGraphWidget::make("travels")
        );
}

# buildWidgetsLayout() signature has changed to buildDashboardLayout(DashboardLayout $dashboardLayout)

The $dashboardLayout parameter is a proxy object with the needed ->addRow(...) and ->addFullWidthWidget(...) functions.

Example: you need to refactor you code from this:

function buildWidgetsLayout(): void
{
    $this
        ->addRow(function(DashboardLayoutRow $row) {
            $row->addWidget(6, "types_pie")
                ->addWidget(6, "features_bars");
        });
}

To this:

function buildDashboardLayout(DashboardLayout $dashboardLayout): void
{
    $dashboardLayout
        ->addRow(function(DashboardLayoutRow $row) {
            $row->addWidget(6, "types_pie")
                ->addWidget(6, "features_bars");
        });
}

# API changes on Commands

There are various changes on Commands, but it's mainly code reorganization and function renaming, for better clarity and easier configuration.

# New Command declaration

Commands must now be declared in dedicated function: getInstanceCommands() (EntityList and Show Page) , getEntityCommands() (EntityList), and getDashboardCommands() (Dashboard) — previously they were declared in the build...Config() method.

For example, here's a 6.0 EntityList (everything in the buildListConfig() function):

function buildListConfig(): void
{
    $this->setInstanceIdAttribute("id")
        ->setSearchable()
        ->setDefaultSort("name", "asc")
        ->addFilter("type", SpaceshipTypeFilter::class)
        ->addFilter("pilots", SpaceshipPilotsFilter::class)
        ->addEntityCommand("synchronize", SpaceshipSynchronize::class)
        ->addEntityCommand("reload", SpaceshipReload::class)
        ->addInstanceCommand("message", SpaceshipSendMessage::class)
        ->addInstanceCommand("preview", SpaceshipPreview::class)
        ->addInstanceCommandSeparator()
        ->addInstanceCommand("external", SpaceshipExternalLink::class);
}

And the same EntityList in 7.0: Commands are no longer in the buildListConfig function:

function getEntityCommands(): ?array
{
    return [
        "synchronize" => new SpaceshipSynchronize(),
        SpaceshipReload::class
    ];
}

function getInstanceCommands(): ?array
{
    return [
        SpaceshipSendMessage::class,
        new SpaceshipPreview(),
        "---",
        SpaceshipExternalLink::class
    ];
}

function buildListConfig(): void
{
    $this->configureInstanceIdAttribute("id")
        ->configureSearchable()
        ->configureDefaultSort("name", "asc");
}

Also notice that:

  • the Command key is now optional
  • the Command separator is a string made of dashes (one is enough)
  • Filters were removed as well, see dedicated section below in this guide

This applies in every class that leverage Commands: EntityList, Show Page and Dashboard.

# new buildCommandConfig() method + removal of old functions: ->description(), ->confirmationText()...

In 6.0, some Command configuration was done overriding some methods, without really knowing which ones are concerned. In 7.0, a new buildCommandConfig() is here to handle all configuration.

Here's a 6.0 example:

class TravelSendEmail extends InstanceCommand
{
    public function label(): string
    {
        return "Send email";
    }

    public function formModalTitle(): string
    {
        return "Send email";
    }

    public function description(): string
    {
        return "Will pretend to send an email to all the passengers of this flight.";
    }
    
    [...]
}

And its 7.0 version:

class class TravelSendEmail extends InstanceCommand
{
    public function label(): string
    {
        return "Send email";
    }
    
    public function buildCommandConfig(): void
    {
        $this->configureFormModalTitle("Send email")
            ->configureDescription("Will pretend to send an email to all the passengers of this flight.");
    }
    
    [...]
}

# New proxy object for form building

Exactly like for SharpForm (see above), you need to go from this in 6.0:

public function buildFormFields(): void
{
    $this
        ->addField(
            SharpFormTextField::make("subject")
                ->setLabel("Subject")
    );
}

To this in 7.0:

public function buildFormFields(FieldsContainer $formFields): void
{
    $formFields
        ->addField(
            SharpFormTextField::make("subject")
                ->setLabel("Subject")
    );
}

# Changes on Filters

# Base Filters are no longer interfaces, but abstract classes

... so you need to change all implements SelectFilter (for instance) to extends SelectFilter.

# New Filter declaration

Similar to Commands, Filters must now be declared in dedicated function: getFilters().

For example, here's a 6.0 EntityList (everything in the buildListConfig() function):

function buildListConfig(): void
{
    $this->setInstanceIdAttribute("id")
        ->setSearchable()
        ->setDefaultSort("name", "asc")
        ->addFilter("type", SpaceshipTypeFilter::class)
        ->addFilter("pilots", SpaceshipPilotsFilter::class);
}

To this:

function getFilters(): ?array
{
    return [
        SpaceshipTypeFilter::class,
        new SpaceshipPilotsFilter() // Filters can be declared as class name or instances 
    ];
}

function buildListConfig(): void
{
    $this->configureInstanceIdAttribute("id")
        ->configureSearchable()
        ->configureDefaultSort("name", "asc");
}

Also notice that the Filter keys (type and pilots, in this example) disappeared

This applies in all classes that leverage Filters: EntityList and Dashboard.

# No more filter key: update your queries

Like seen in the previous point, the string key has been removed for simplicity and to avoid mistakes. So in Sharp 7.0, to extract a filter value from the query params, you'll need to provide the filter's class name.

For example:

function getListData(): array|Arrayable
{
    $pilot = $this->queryParams->filterFor(SpaceshipPilotFilter::class);
    [...]
}

# New buildFilterConfig() method + removal of all old functions: ->isSearchable(), ->isMaster(), ->template()...

Like other classes in Sharp, Filters now have a dedicated buildFilterConfig() method to group all configuration calls. And since we moved from interfaces to abstract classes, it's easier to find all available options with IDE autocompletion: all option methods start with configure[...]().

For example:

public function buildFilterConfig(): void
{
    $this->configureLabel("Ship type")
        ->configureSearchable()
        ->configureRetainInSession();
}

# Changes on global filters configuration

Global filters no longer need keys (in the configuration, where they are declared):

// in config/sharp.php

return [
    [...],
    "global_filters" => [
        CorporationGlobalFilter::class,
    ]
];

Also update your queries accordingly, using full class name instead of key, like for other fields:

function getListData(): array|Arrayable
{
    $spaceships = Spaceship::select("spaceships.*")
        ->where("corporation_id", currentSharpRequest()->globalFilterFor(CorporationGlobalFilter::class))
        ->get();
    [...]
}

# The callback() filter feature was removed

This feature wasn't well designed. It may be replaced in the future is needed.

# Markdown editor refactoring

# SharpFormMarkdownField and SharpFormWysiwygField where removed, and replace by SharpFormEditorField

The two fields were united in one, with the same UX. To migrate a field, you must change its class and add ->setRenderContentAsMarkdown() is necessary:

Example: you need to refactor your SharpFormMarkdownField from this:

function buildFormFields(): void
{
    $this
        ->addField(
            SharpFormMarkdownField::make("description")
                ->setLabel("Description")
                ->setToolbar([
                    SharpFormMarkdownField::B, 
                    SharpFormMarkdownField::I,
                    SharpFormMarkdownField::A,
                ])
                ->setHeight(700)
        );
}

To this:

function buildFormFields(FieldsContainer $formFields): void
{
    $formFields
        ->addField(
            SharpFormEditorField::make("description")
                ->setRenderContentAsMarkdown()
                ->setLabel("Description")
                ->setToolbar([
                    SharpFormEditorField::B, 
                    SharpFormEditorField::I,
                    SharpFormEditorField::A,
                ])
                ->setHeight(700)
        );
}

Same for SharpFormWysiwygField, except for the ->setRenderContentAsMarkdown().

# The MarkdownAttributeTransformer no more has a dedicated method to handle embedded images

Previously you would have to call ->handleImages() on the transformer to configure image embedding: this is now implied, due to the <x-sharp-media> refactoring. Simply remove this call.

# sharp_markdown_thumbnails() and sharp_markdown_embedded_files() helpers were removed

Replaced by the new <x-sharp-content> component: see dedicated documentation.

# Changes on testing

# Commands should be called via class name

As a result of the Command declaration refactoring, you may now use the full command class name instead of its key to test it:

$this
    ->callSharpInstanceCommandFromList(
        "spaceship", 22, SpaceshipSendMessage::class
    )
    ->assertOk();