Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sharp Laravel Package

code16/sharp

Code-driven CMS framework for Laravel (PHP 8.3+/Laravel 11+). Build admin/CMS sections with a clean UI and strong DX: CRUD with validation, search/sort/filter, bulk or custom commands, and authorization—no front-end code required, data-agnostic.

View on GitHub
Deep Wiki
Context7

outline: [1, 2]

Upgrading from 8.x to 9.x

This is a very big release, with a lot of changes. We try to limit breaking changes, but there are some... This guide will help you to upgrade your Sharp 8.x app to Sharp 9.x.

General

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 --tag=sharp-assets --force
php artisan view:clear

Update your composer.json

The command used to publish sharp's assets changed, you should update your composer.json:

{
  "scripts": {
    "post-autoload-dump": [
      [...],
-      "[@php](https://github.com/php) artisan vendor:publish --provider='Code16\\Sharp\\SharpServiceProvider' --tag=assets --force",
+      "[@php](https://github.com/php) artisan vendor:publish --tag=sharp-assets --force"
    ]
  }
}

Deprecated methods have been removed

  • Entity List: deprecated buildListFields() and buildListLayout() were removed, use buildList() instead
  • Form: deprecated delete() method was removed (since it was moved to show / entity list in 8.x)

New way to configure Sharp, via a dedicated builder class

The config/sharp.php file was entirely removed in favor of a dedicated builder class. This is not a breaking change since the config file is still supported, but deprecated, so you are encouraged to migrate to the new builder class.

To migrate, you should first create a new Service Provider which extends Code16\Sharp\SharpAppServiceProvider and implements the configureSharp() method:

use Code16\Sharp\SharpAppServiceProvider;

class MySharpServiceProvider extends SharpAppServiceProvider
{
    protected function configureSharp(SharpConfigBuilder $config): void
    {
        $config
            ->setName('My project')
            ->declareEntity(PostEntity::class)
            // ...
    }
}

Report all you configuration using the API of this new SharpConfigBuilder class. It should be pretty straightforward, as all the methods are named after the config keys they replace. For example:

In 8.x:

// Old config/sharp.php
return [
    'name' => 'Demo project',
    'custom_url_segment' => 'sharp',
    'display_breadcrumb' => true,
    'entities' => [
        'posts' => \App\Sharp\Entities\PostEntity::class,
    ],

    'global_filters' => fn () => auth()->id() === 1 ? [] : [\App\Sharp\DummyGlobalFilter::class],

    'search' => [
        'enabled' => true,
        'placeholder' => 'Search for posts or authors...',
        'engine' => \App\Sharp\AppSearchEngine::class,
    ],

    'menu' => \App\Sharp\SharpMenu::class,

    // ...
];

In 9.x:

class MySharpServiceProvider extends SharpAppServiceProvider
{
    protected function configureSharp(SharpConfigBuilder $config): void
    {
        $config
            ->setName('Demo project')
            ->setCustomUrlSegment('sharp')
            ->setDisplayBreadcrumb()
            ->declareEntity(PostEntity::class)
            ->addGlobalFilter(DummyGlobalFilter::class) // The auth()->id() === 1 no longer can be handled here, as the auth context is yet not available. Use the new authorize() method of the global filter instead.
            ->enableGlobalSearch(AppSearchEngine::class, 'Search for posts or authors...')
            ->setMenu(SharpMenu::class)
            // ...
    }
}

::: warning Be sure to register this new Service Provider in your app. :::

Middleware updates (legacy config only)

Due to migration to inertia, three middleware must be added to the config. Also, SetSharpLocale must be removed from api group.

::: info If you migrated to the new config builder class, you should be ok unless you have explicitly overridden the whole middleware list. :::

Here is the impact on the deprecated config file:

// config/sharp.php

return [
    'middleware' => [
        'common' => [
            // ...
            \Code16\Sharp\Http\Middleware\HandleGlobalFilters::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class, // <- be sure to place this one after HandleGlobalFilters
        ],
        'web' => [
            // ...
            \Code16\Sharp\Http\Middleware\HandleSharpErrors::class,
            \Code16\Sharp\Http\Middleware\HandleInertiaRequests::class,
        ],
        'api' => [
           // To remove :
           // \Code16\Sharp\Http\Middleware\Api\SetSharpLocale::class, 
        ]
    ],
]

Migration to blade-icons

In 8.x, menu icons were FontAwesome classes like fa fa-user or fas fa-user. Now icons must be blade-icons icon names with its associated package installed.

Icons are not required but if you want to keep FontAwesome, you can install the following package:

composer require owenvoke/blade-fontawesome

And rename old icon names to blade-fontawesome names in your SharpMenu :

# Solid icons
- ->addEntityLink('entity', 'Entity', 'fas fa-user')
- ->addEntityLink('entity', 'Entity', 'fa fa-user')
+ ->addEntityLink('entity', 'Entity', logo: 'fas-user')

# Regular (outline) icons
- ->addEntityLink('entity', 'Entity', 'far fa-envelope')
- ->addEntityLink('entity', 'Entity', 'fa fa-envelope-o')
+ ->addEntityLink('entity', 'Entity', logo: 'far-envelope')

# Brand icons
- ->addEntityLink('entity', 'Entity', 'fa fa-github')
- ->addEntityLink('entity', 'Entity', 'fab fa-github')
+ ->addEntityLink('entity', 'Entity', logo: 'fab-github')

If you were using old fontawesome 4 icons you may need to rename them.

Restore fontawesome icons default behavior (optional)

As the default behavior of blade-icons (which uses an svg tag) differs from the previous behavior of FontAwesome’s <i/> tag (which was inline and matched the font size), you may need to adjust the blade-icons configuration to replicate the original behavior.

First, publish the FontAwesome blade-icons configuration file:

php artisan vendor:publish --tag=blade-fontawesome-config

Then add default attributes for each FontAwesome classes (solid, regular, brands, and optionally pro):

// file: config/blade-fontawesome.php
return [
    // ...
    'regular' => [
      // ...
      'attributes' => [
          'width' => '1rem',
          'height' => '1rem',
          'style' => 'display: inline;'
      ],
    ],
];

No more Bootstrap.css / Font awesome classes

You may be using Custom HTML (entity list row, editor embeds, form HTML field, form Autocomplete item template, page alerts) with CSS classes that was present in Sharp 8.x but that don't exist anymore:

  • If you were using bootstrap classes like row / col / badge in HTML content. These are no longer available, you can either :
    • Convert to inline CSS (for bootstrap grid classes / utilities)
    • Inject a custom CSS file as described here
  • If you were using inline Font Awesome icons like <i class="fas fa-user">
    • Using blade-fontawesome component like : <x-fas-user style="width: 1rem; height: 1rem" />. In Sharp 9.x all templates are now Blade.
    • For <i class="fas fa-user"> inside a custom transformer of an EntityList field, you must now do Blade::render('<x-fas-user style="width: 1rem; height: 1rem; display:inline;" />')

Page Alerts (aka global messages) are not based on Vue templates anymore

This part has been entirely rewritten, and will need substantial changes in your code.

In 8.x and below, you were asked to configure page alerts in the buildConfig() method; and if your page alert was displaying dynamic data, you had to use a custom transformer to inject the data in the page alert. All of this was removed, in favor of a much simpler "back only" system. Here’s an example of a page alert in a Show Page (this is the same in Form, Dashboard, Entity List, Embed and Command cases):

class MyShow extends SharpShow
{
    // ...
    
    protected function buildPageAlert(PageAlert $pageAlert): void
    {
        $pageAlert
            ->setLevelInfo()
            ->setMessage(function (array $data) {
                return $data['is_planned']
                    ? 'This post is planned for publication, on ' . $data['published_at']
                    : null;
            });
    }
}

As you can see, this new buildPageAlert() method takes a PageAlert object as parameter to work with. You'll have access to the $data array returned by your find() or getListData() method, to inject dynamic data in your page alert if needed. Vue templates are no longer handled, as the page alert is now rendered on the back only.

See global page alert documentation for more detail.

Related models handling in custom transformers was fixed (and potentially breaking)

This bug fix potentially brings a breaking change: if you were using a custom transformer to handle related models, you may have to update it.

Here’s code which will work in Sharp 8.x and below:

$this
    ->setCustomTransformer('customer:name', function ($value, $instance, $attribute) { 
        return $instance->customer->name; // $instance is the Order
    })
    ->transform(Order::find(1))

Is has to be rewritten like this in Sharp 9.x:

$this
    ->setCustomTransformer('customer:name', function ($value, $instance, $attribute) { 
        return $instance->name; // $instance is the Customer, as it should be.
    })
    ->transform(Order::find(1))

The main difference is that the $instance parameter refers to the related model, not the main model anymore. To summarize:

In 8.x:

$value: 'Joe Doe'
$instance: // the Order instance
$attribute: 'customer:name'

In 9.x:

$value: 'Joe Doe'
$instance: // the **Customer** instance
$attribute: 'name'

Thumbnails custom filters must be refactored to Modifiers

First, if you defined custom filters classes for your thumbnails, you must refactor it to the new ThumbnailModifier API, which is very close:

In 8.x

class MyFilter extends ThumbnailFilter
{
    public function applyFilter(Image $image): Image
    {
        // ...
    }
}

In 9.x

use Code16\Sharp\Form\Eloquent\Uploads\Thumbnails\ThumbnailModifier;
use Intervention\Image\Interfaces\ImageInterface;

class MyModifier extends ThumbnailModifier
{
    public function apply(ImageInterface $image): ImageInterface
    {
        // ...
    }
}

And secondly, in 9.x you can’t pass modifier's parameters as an array anymore:

In 8.x

$book->cover->thumbnail(100, 100, ['fit'=>['w'=>100, 'h'=>100]]);

In 9.x

$book->cover->thumbnail(100, 100, [new FitModifier(100, 100)]);

// or with the new fluent API
$book->cover->thumbnail()
    ->addModifier(new FitModifier(100, 100))
    ->make();

currentSharpRequest() is now deprecated in favor of sharp()->context() helper

The currentSharpRequest() helper is now deprecated, and will be entirely removed in a future version. You should migrate your code to use the sharp()->context() helper instead (see the dedicated documentation).

SharpAuthenticationCheckHandler is now deprecated in favor of viewSharp Gate

The use of a SharpAuthenticationCheckHandler is now deprecated, and will be entirely removed in a future version. You should migrate your handler to a Gate:

In 8.x:

class MySharpAuthenticationCheckHandler implements SharpAuthenticationCheckHandler
{
    public function check(Authenticatable $user): bool;
    {
        return $user->is_sharp_admin;
    }
}

In 9.x:

class AppServiceProvider extends ServiceProvider
{
    // ...

    public function boot(): void
    {
        Gate::define('viewSharp', fn ($user) => $user->is_sharp_admin);
    }
}

::: tip You should place this code in the new Sharp dedicated Service Provider you will create to configure your Sharp app, overriding the declareAccessGate() method. See the dedicated documentation. :::

Next, the sharp.auth.check_handler config key can be safely removed from your config/sharp.php file (in case you have not yet migrated to the dedicated builder class, see above), along with the SharpAuthenticationCheckHandler implementation class.

Injected CSS must now be loaded with the SharpConfigBuilder

In 8.x,

// config/sharp.php

return [
    'extensions' => [
       'assets' => [
          'strategy' => 'vite',
          'head' => [
             'resources/css/sharp.css',
          ],
       ],
    ],
];

In 9.x :

class MySharpServiceProvider extends SharpAppServiceProvider
{
    protected function configureSharp(SharpConfigBuilder $config): void
    {
        $config
            ->loadViteAssets(['resources/css/sharp.css']) // to load a CSS file built with Vite
            ->loadStaticCss(asset('/css/sharp.css')) // Or to load a static CSS file
    }
}

All test assertions were removed

All assertions, like for instance assertSharpHasAuthorization, were removed because they were clumsy and not really useful. You must remove them from your tests, and use standard comparisons instead — although in real world, it’s easier and cleaner to just check the return status (ie: assertOk()) and check, if needed, the consequences in the database directly.

This means you also need to remove all $this->initSharpAssertions() calls from your tests.

Of course, the test helpers remain available, see the dedicated testing documentation.

Also take note that the withSharpCurrentBreadcrumb() method is now deprecated, in favor of the new withSharpBreadcrumb() method also documented in the section linked above.

Dashboard

SharpWidgetPanels are now based on blade template

In a similar way to Page Alerts, we abandoned Vue templates for custom SharpWidgetPanels in favor of blade templates. This is a breaking change, as the setTemplatePath(...)and setInlineTemplate(...) methods were removed, placed by a unique setTemplate(View $template).

There's almost nothing to change on the PHP side:

In 8.x:

class MyDashboard extends SharpDashboard
{
    // ...
    
    protected function buildWidgets(WidgetsContainer $widgetsContainer): void
    {
        $widgetsContainer
            ->addWidget(
                SharpPanelWidget::make('my_custom_panel')
                    ->setTitle('My custom panel')
                    ->setTemplatePath('sharp.templates.my_template') // Must be an existing **vue file** 
            );
    }
}

In 9.x:

class MyDashboard extends SharpDashboard
{
    // ...
    
    protected function buildWidgets(WidgetsContainer $widgetsContainer): void
    {
        $widgetsContainer
            ->addWidget(
                SharpPanelWidget::make('my_custom_panel')
                    ->setTitle('My custom panel')
                    ->setTemplate(view('sharp.templates.my_template')) // Must be an existing **blade view**
            );
    }
}

The main change is the template itself, which must be a blade view now.

See panel widget documentation for more detail.

Entity Lists

Entity Lists have a new authorization: reorder

Previously the reorder authorization was handled by the update authorization, which can lead to unwanted effects. You should now declare a specific reorder authorization in your Policies:

class PostPolicy extends SharpEntityPolicy
{
    public function reorder($user): bool
    {
        return $user->isEditor();
    }
    
    // ...
}

Entity List's setWidth() method as a new signature (non-breaking change: old signature is still supported)

The ->setWith($width) method now expects a percentage value, expressed as a string (eg: '20' or '20%'), a float (eg: .2 for 20%) or an integer (eg: 20 for 20%). The old signature use to accept a 1 to 12 integer (12-grid): it is still supported (Sharp will transform a 6 in 50%), but deprecated, and you are strongly encourage to migrate to the new signature.

Methods to handle Entity List columns width on small screen were deprecated

The ->setWithOnSmallScreen($width) and ->setWithOnSmallScreenFill() methods were deprecated, because they are no more used in the new front table UI system. You can safely remove them, Sharp will rely on the setWidth($width) method, or, even easier, will deduce width based on content like a regular table.

The ->hideOnSmallScreens() method remains.

All filters must be declared in order to be used

In Sharp 8.x it was possible, in an Embedded Entity List (EEL) case, to use a filter without declaring it. This was a kind of bug, and has been fixed in 9.x: all filters must be declared in the getFilters() method.

Consider this code in 8.x, where we have a PostShow that embeds a PostBlockList as an EEL:

class PostShow extends SharpShow
{
    protected function buildShowFields(FieldsContainer $showFields): void
    {
        $showFields
            ->addField(SharpShowTextField::make('title')->setLabel('Title'))
            ->addField(
                SharpShowEntityListField::make('blocks')
                    ->setLabel('Blocks')
                    ->hideFilterWithValue('post', fn($instanceId) => $instanceId)
            );
    }
    // ...
}

class PostBlockList extends SharpEntityList
{
    // ...

    protected function getFilters(): ?array
    {
        return [
            // Nothing there
        ];
    }

    public function getListData(): array|Arrayable
    {
        return $this->transform(
            PostBlock::query()
                ->where('post_id', $this->queryParams->filterFor('post'))
                ->get()
        );
    }
}

We can see that PostBlockList does not define any Filter, but uses one in the getListData() method, valued by the PostShow via hideFilterWithValue(). In 9.x, this won't work as the Filter must be declared in the getFilters() method. There is a new way to quickly declare such Filters that are not meant to be shown to the user, HiddenFiler:

In 9.x

use \Code16\Sharp\EntityList\Filters\HiddenFilter;

class PostBlockList extends SharpEntityList
{
    // ...

    protected function getFilters(): ?array
    {
        return [
            HiddenFilter::make('post')
        ];
    }
    
    // ... The rest is the same
}

You can of course instead declare a real Filter.

Select filter configureTemplate() has been dropped

If you were using this method, you must do the string transformation in the label of each value.

New performance optimization for Commands and Policies in Entity List (n+1)

This is not a breaking change, in fact you can ignore this step entirely, but since it's can lead to a significative performance boost, this is worth mentioning: you can now quite easily implement a caching mechanism of instances for your Commands and Policies in Entity List.

Forms & Shows

Form and Show layout methods were renamed (old ones are deprecated)

The ->withSingleField() method was deprecated, in favor of:

  • ->withField(string $fieldKey) for simple fields
  • ->withListField(string $fieldKey, Closure $subLayoutCallback) for List fields, which requires a sub-layout handled by a callback.

The method ->withFields(string ...$fieldKeys), used for multiple fields layout, remains unchanged.

Form and Show Fields are now formatted even if you don’t transform them

This shouldn’t cause any trouble, as this is a fix, but it could break unorthodox code: field formatters (which are used to format the field value for the frontend) are now properly and always called before displaying data to the front, even if you don’t transform your data with $this->transform() method.

Custom form / show fields are not supported anymore

In 8.x, you could define custom field by creating a Vue component (in form or in show) but this is not supported anymore.

In the initial 9.0 release, there isn't a straightforward alternative, but we are looking for ways to easily integrate Alpine / Livewire component. If you only need to render static blade file you can use SharpFormHtmlField or SharpShowTextField.

Forms

New validation system (and deprecation of the old one)

The validation system has been revamped in version 9.x to align better with Laravel and improve consistency.

You may now define your validation rules in two ways:

  • implement new rules() and messages() methods in your SharpForm or Command; those methods accepts an optional $formattedData parameter which represents the... formatted posted data.
  • or make a call to ->validate(array $data, array $rules).

In consequence, Form Validators are now deprecated: the $formValidatorClass property in SharpForm is deprecated. You are strongly encouraged to switch to the rules() method.

This code in 8.x:

class PostForm extends SharpForm
{
    protected ?string $formValidatorClass = PostFormValidator::class;

    // ...
}

class PostFormValidator extends SharpFormRequest
{
    public function rules(): array
    {
        return [
            'title' => 'required',
            'content' => 'required',
        ];
    }
}

Should be rewritten in this in 9.x:

class PostForm extends SharpForm
{
    public function rules(): array
    {
        return [
            'title' => 'required',
            'content' => 'required',
        ];
    }

    // ...
}

Or:

class PostForm extends SharpForm
{
    // ...

    public function update($id...
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament