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.
Get new assets, clear cache
This is true for every update: be sure to grab the latest assets and to clear the view cache:
php artisan vendor:publish --provider="Code16\Sharp\SharpServiceProvider" --tag=assets
php artisan view:clear
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
.
configure...
All configuration methods have now a name prefixed with 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()
setBreadcrumbCustomLabelAttribute()
->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");
}
->addColumn()
no longer has optional parameter for small screens
EntityList's 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 [
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.
buildCommandConfig()
method + removal of old functions: ->description()
, ->confirmationText()
...
new 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);
[...]
}
buildFilterConfig()
method + removal of all old functions: ->isSearchable()
, ->isMaster()
, ->template()
...
New 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();
[...]
}
callback()
filter feature was removed
The This feature wasn't designed well. 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()
.
MarkdownAttributeTransformer
no more has a dedicated method to handle embedded images
The If you allow embed images in a markdown field, 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.
Before:
function find($id): array
{
return $this
->setCustomTransformer(
"description",
(new MarkdownAttributeTransformer())->handleImages(200)
)
->transform([...]);
}
After:
function find($id): array
{
return $this
->setCustomTransformer(
"description",
new MarkdownAttributeTransformer()
)
->transform([...]);
}
How to display markdown with embed files and images with helpers has changed
If you allow embed images or files in a markdown field, previously you would need to use respectively sharp_markdown_thumbnails()
and sharp_markdown_embedded_files()
to display embedded images and files in the public site. Those two helpers were removed.
You now need to replace them by the new <x-sharp-content>
component: see dedicated documentation.
Before with embed images:
<div>
{!! sharp_markdown_thumbnails($object->my_markdown_field, "mw-100", 775) !!}
</div>
or with embed files:
<div>
{!! sharp_markdown_embedded_files($object->my_markdown_field) !!}
</div>
After:
<div>
<x-sharp-content>
{!! $object->my_markdown_field !!}
</x-sharp-content>
</div>
Handle existing data
If you allow embed images or files in a markdown field, you probably stored them in database. With previous format, markdown with embed objects looked something like this:
# My title
Some text
![](local:/data/img/markdown/my_image.jpg)
More text
![](local:/data/img/markdown/my_document.pdf)
Due to the <x-sharp-media>
refactoring, markdown with embed objects looks something like:
# My title
Some text
<x-sharp-image name="my_image.jpg" path="data/img/markdown/my_image.jpg" disk="local">
</x-sharp-image>
More text
<x-sharp-file name="my_document.pdf" path="data/files/markdown/my_document.pdf" disk="local">
</x-sharp-file>
If you want to keep displaying old markdown, you will have to convert embed objects from old to new format in all your stored markdown fields. This could be achieved manually or automated with a custom script based on preg_replace_callback.
New Entity classes (SharpEntity)
Sharp 7 brings a new config format, along with the SharpEntity. In short, this means the sharp.php
config file is lighter since all the entity config (list, form, policy, show...) is now declared in a dedicated entity class. This is fully documented here, and it's NOT A BREAKING CHANGE, since the legacy config format is still handled with Sharp 7 (but it's clearly deprecated).
New config format
In the config of a Sharp 7 project, we just declare a SharpEntity class for entities and dashboard:
// config/sharp.php
return [
// ...
"entities" => [
"spaceship" => \App\Sharp\Entities\SpaceshipEntity::class,
"pilot" => \App\Sharp\Entities\PilotEntity::class,
],
"dashboards" => [
"company_dashboard" => \App\Sharp\Entities\CompanyDashboardEntity::class,
],
//...
];
SharpEntity
's subclasses are responsible for the list, form, show... declaration (see documentation). Note that as stated before, you can keep the old declaration way in the config file, or even mix the new and old methods, but the old one is deprecated and may be removed in a future release.
Impact on Validators
Validators declaration moved from config file to where it belongs, in the Form itself:
class SpaceshipSharpForm extends SharpForm
{
protected ?string $formValidatorClass = SpaceshipSharpValidator::class;
// ...
}
See related documentation, and do note that the Sharp 6 way of declare it (in config/sharp.php) is still handled as legacy (but deprecated).
Impact on policies
Entity and Dashboard Policies must now extend Code16\Sharp\Auth\SharpEntityPolicy
, where the methods slightly changed (typehinting mostly). This is not a BC, since the legacy Sharp 6 format is still handled, but it's deprecated.
The permission method to see a Dashboard was renamed from view()
to entity()
, to be consistent:
class CompanyDashboardPolicy extends SharpEntityPolicy
{
public function entity($user): bool
{
return $user->hasGroup("boss");
}
}
Impact on global authorizations
Like for policies, global authorizations were moved to the SharpEntity
implementation, and renamed $prohibitedActions
(see documentation). Legacy declaration is still handled, except on one point (which was kinda buggy anyway): it's not possible to declare a global authorization to be true
, meaning you can only globally forbid actions. The new $prohibitedActions
is way clearer on this.
New way of configure the Menu
Sharp 7 introduce a new way of defining the Menu: in a class instead of in the config file. This is documented here, and do note that the Sharp 6 way is still supported.
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();