witify / support
Base classes of the Witify Laravel applications: actions, controllers, resources, query builders, confirmation flow, rich text sanitization and module providers.
Requires
- php: ^8.2
- illuminate/auth: ^11.0|^12.0
- illuminate/broadcasting: ^11.0|^12.0
- illuminate/cache: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/notifications: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/session: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/translation: ^11.0|^12.0
- symfony/html-sanitizer: ^7.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Base classes shared by the Witify Laravel applications: the Action contract, the base controller with its confirmation flow, the API resource and query builder bases, the module service provider with its shared-data and user-notification registries, the rich text sanitizer, the notification mail message and database channel, and the IsResource contract that feeds resource_data to the frontend.
Every other witify/* package that needs these classes depends on this one. Applications generated by the Sprintify template consume it instead of their former src/Support copies.
Requirements
| PHP | 8.2 to 8.4 |
| Laravel | 11 and 12 |
| Database | any Eloquent connection; DatabaseChannel writes to the application's notifications table |
symfony/html-sanitizer is installed with the package.
Installation
composer require witify/support
The service provider is discovered automatically. It binds the two registries (config::sharedData, config::userNotifications), merges the default config and loads the support:: translations.
Publish the config only when a default needs to change, the translations only to reword them:
php artisan vendor:publish --tag="support-config" php artisan vendor:publish --tag="support-translations"
Tell the package how to sign the mails, in a service provider of the application:
use Witify\Support\Host; Host::resolveCompanyNameUsing(fn (): ?string => settings()->company_name);
Without a resolver the mails are signed with support.company_name, then app.name.
What the package provides
| Class | Role |
|---|---|
Action\Action |
handle() contract of the single-purpose actions |
Controller\Controller |
Base controller: user(), rateLimit(), confirmRequest() |
Resource\BaseResource |
JSON resource that appends can: {view, update, delete} from the policies |
QueryBuilder\BaseQueryBuilder |
Eloquent builder base: protect(), search(), dateBetween(), exclude(), deterministic pagination |
Search\SearchQuery, Search\BelongsToSearchConstraint, Search\SearchConstraintInterface |
Keyword search over columns, expressions, callbacks and relations |
Exception\BusinessRuleException, ReportableBusinessRuleException, RateLimitException, ShouldntReportOnRequest |
JSON exceptions (400, 429) and the marker that keeps them out of the error reporter |
Http\Confirmation\Actions\ConfirmRequestAction, Http\Confirmation\ConfirmationHttpException, Http\Middlewares\ClearConfirmationFlow |
Confirmation flow: the first call answers 428, the replay with the confirmation headers runs the action |
Model\IsResource, Model\IsResourceTrait |
Title, subtitle, icon, color and admin path of a model, appended as resource_data |
Model\SanitizesHtmlTrait, Html\RichTextSanitizer |
Sanitizes the rich text fields of a model on every save against the html-sanitizer allow-list |
Mail\MailMessage |
Notification mail with the greeting and the company signature |
NotificationChannels\DatabaseChannel, Events\NewNotification |
Database channel writing the wide notifications row and broadcasting notifications.created on the user's private channel |
ModuleServiceProvider |
Base provider of the modules: routes, policies, translations, shared data and user notifications |
SharedData\SharedData, UserNotifications\UserNotifications |
The two registries the modules fill and the application reads |
Host |
Resolvers for the values the package takes from the application |
Contracts the frontend relies on
These shapes are read by the Vue applications and by the code the Architect generator produces. They do not change inside a major version.
resource_dataon everyIsResourcemodel:title,subtitle,icon,color,admin_to,admin_url,model_type.admin_urlissupport.admin_url(default/admin) followed bygetResourceAdminTo().colorisgrayunless the model overridesgetResourceColor().canon everyBaseResource:view,update,delete, from the policy of the model. OverridecanDo()to add abilities.search()treats the keyword as a literal (%and_match themselves), relies on the collation for case-insensitivity and hands a callback the lowercased keyword.- The confirmation flow: a 428 response with
code: "confirmation_required"and aconfirmationobject (flow,token,title,message,errorMessage,confirmText,cancelText,requiresPassword). The client replays the same request withX-Confirmation-Flow,X-Confirmation-Tokenand, when a password is required,X-Confirmation-Password. RegisterClearConfirmationFlowon the web middleware group so a completed flow leaves the session. Three wrong passwords lock the action for the user who typed them, and a right one wipes the count. NewNotificationbroadcastsnotifications.createdonprivate-user.{id}. The channel name comes fromsupport.notifications.channel; setsupport.notifications.broadcasttofalsein an application without a WebSocket server.
Shared data
Modules put what the frontend needs into the registry from their provider:
class OrderServiceProvider extends ModuleServiceProvider { public function name(): string { return 'orders'; } public function sharedData(): array { return ['statuses' => OrderStatus::all()]; } }
The application collects the registry into its window.Laravel payload, together with its own values:
$data = array_merge(app(SupportServiceProvider::SHARED_DATA)->all(), [ 'app' => [...], 'models' => [...], ]);
The registry no longer calls the application's ShareDataToClient: the application merges the two, so the package never imports application code.
Configuration
| Key | Default | Purpose |
|---|---|---|
support.admin_url |
/admin |
prefix of resource_data.admin_url |
support.company_name |
null, then app.name |
signature of the mails when no resolver is set |
support.notifications.channel |
user.{id} |
private channel of NewNotification |
support.notifications.broadcast |
true |
broadcast after the database channel stored a notification |
html-sanitizer.* |
the TipTap allow-list of sprintify-ui | elements, attributes and schemes kept by RichTextSanitizer |
Translations live under the support:: namespace: messages (unauthorized action, too many requests), confirmation (titles, buttons, password throttle) and mail (greeting, regards).
Migrating an application from src/Support
composer require witify/support.- Delete the classes listed above from
src/Supportandmodules/Utils/ModuleServiceProvider.php; keep the application-specific ones (ShareDataToClient,UserPermissions, the middlewares, the rules, ...). - Replace the imports:
Support\Action\ActionbecomesWitify\Support\Action\Action, and so on.Support\Config\ShareData\SharedDatabecomesWitify\Support\SharedData\SharedData,Support\Config\UserNotifications\*becomesWitify\Support\UserNotifications\*,Modules\Utils\ModuleServiceProviderbecomesWitify\Support\ModuleServiceProvider,Modules\Notification\Events\NewNotificationbecomesWitify\Support\Events\NewNotification. - Remove the
config::sharedDataandconfig::userNotificationssingletons from the application provider: the package binds them. - Make
ShareDataToClientmergeapp(SupportServiceProvider::SHARED_DATA)->all()and point the layout at it. - Register
Host::resolveCompanyNameUsing(). - Update the Architect stubs so the generated code imports the package classes.
Controller::user() now returns Illuminate\Contracts\Auth\Authenticatable; annotate or check the type where the application relies on its own User model.
Changelog and upgrades
See the CHANGELOG of the monorepo. Every package shares the same version number.
Contributing
This repository is a read-only split of witify/packages. Open pull requests there, in packages/support.