tipo: modificación del cpanel y gitignore de la carpeta vendor

This commit is contained in:
2026-07-07 11:35:09 -06:00
parent a0c91dc567
commit 676ef0d506
8510 changed files with 1132803 additions and 4 deletions
+9
View File
@@ -0,0 +1,9 @@
## MIT License
Copyright © Caleb Porzio
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
<p align="center"><img width="300" src="/art/logo.svg" alt="Livewire Logo"></p>
<p align="center">
<a href="https://packagist.org/packages/livewire/livewire">
<img src="https://poser.pugx.org/livewire/livewire/d/total.svg" alt="Total Downloads">
</a>
<a href="https://packagist.org/packages/livewire/livewire">
<img src="https://poser.pugx.org/livewire/livewire/v/stable.svg" alt="Latest Stable Version">
</a>
<a href="https://packagist.org/packages/livewire/livewire">
<img src="https://poser.pugx.org/livewire/livewire/license.svg" alt="License">
</a>
</p>
## Introduction
Livewire is a full-stack framework for Laravel that allows you to build dynamic UI components without leaving PHP.
## Official Documentation
You can read the official documentation on the [Livewire website](https://livewire.laravel.com/docs).
## Contributing
<a name="contributing"></a>
Thank you for considering contributing to Livewire! You can read the contribution guide [here](.github/CONTRIBUTING.md).
## Code of Conduct
<a name="code-of-conduct"></a>
In order to ensure that the Laravel community is welcoming to all, please review and abide by Laravel's [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
<a name="security-vulnerabilities"></a>
Please review [our security policy](https://github.com/livewire/livewire/security/policy) on how to report security vulnerabilities.
## License
<a name="license"></a>
Livewire is open-sourced software licensed under the [MIT license](LICENSE.md).
+58
View File
@@ -0,0 +1,58 @@
{
"name": "livewire/livewire",
"description": "A front-end framework for Laravel.",
"license": "MIT",
"authors": [
{
"name": "Caleb Porzio",
"email": "calebporzio@gmail.com"
}
],
"require": {
"php": "^8.1",
"illuminate/database": "^10.0|^11.0|^12.0",
"illuminate/routing": "^10.0|^11.0|^12.0",
"illuminate/support": "^10.0|^11.0|^12.0",
"illuminate/validation": "^10.0|^11.0|^12.0",
"league/mime-type-detection": "^1.9",
"symfony/console": "^6.0|^7.0",
"symfony/http-kernel": "^6.2|^7.0",
"laravel/prompts": "^0.1.24|^0.2|^0.3"
},
"require-dev": {
"psy/psysh": "^0.11.22|^0.12",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^10.4|^11.5",
"laravel/framework": "^10.15.0|^11.0|^12.0",
"orchestra/testbench": "^8.21.0|^9.0|^10.0",
"orchestra/testbench-dusk": "^8.24|^9.1|^10.0",
"calebporzio/sushi": "^2.1"
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Livewire\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\": "vendor/orchestra/testbench-core/laravel/app",
"Tests\\": "tests/",
"LegacyTests\\": "legacy_tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Livewire\\LivewireServiceProvider"
],
"aliases": {
"Livewire": "Livewire\\Livewire"
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
+186
View File
@@ -0,0 +1,186 @@
<?php
return [
/*
|---------------------------------------------------------------------------
| Class Namespace
|---------------------------------------------------------------------------
|
| This value sets the root class namespace for Livewire component classes in
| your application. This value will change where component auto-discovery
| finds components. It's also referenced by the file creation commands.
|
*/
'class_namespace' => 'App\\Livewire',
/*
|---------------------------------------------------------------------------
| View Path
|---------------------------------------------------------------------------
|
| This value is used to specify where Livewire component Blade templates are
| stored when running file creation commands like `artisan make:livewire`.
| It is also used if you choose to omit a component's render() method.
|
*/
'view_path' => resource_path('views/livewire'),
/*
|---------------------------------------------------------------------------
| Layout
|---------------------------------------------------------------------------
| The view that will be used as the layout when rendering a single component
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
| In this case, the view returned by CreatePost will render into $slot.
|
*/
'layout' => 'components.layouts.app',
/*
|---------------------------------------------------------------------------
| Lazy Loading Placeholder
|---------------------------------------------------------------------------
| Livewire allows you to lazy load components that would otherwise slow down
| the initial page load. Every component can have a custom placeholder or
| you can define the default placeholder view for all components below.
|
*/
'lazy_placeholder' => null,
/*
|---------------------------------------------------------------------------
| Temporary File Uploads
|---------------------------------------------------------------------------
|
| Livewire handles file uploads by storing uploads in a temporary directory
| before the file is stored permanently. All file uploads are directed to
| a global endpoint for temporary storage. You may configure this below:
|
*/
'temporary_file_upload' => [
'disk' => null, // Example: 'local', 's3' | Default: 'default'
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
],
/*
|---------------------------------------------------------------------------
| Render On Redirect
|---------------------------------------------------------------------------
|
| This value determines if Livewire will run a component's `render()` method
| after a redirect has been triggered using something like `redirect(...)`
| Setting this to true will render the view once more before redirecting
|
*/
'render_on_redirect' => false,
/*
|---------------------------------------------------------------------------
| Eloquent Model Binding
|---------------------------------------------------------------------------
|
| Previous versions of Livewire supported binding directly to eloquent model
| properties using wire:model by default. However, this behavior has been
| deemed too "magical" and has therefore been put under a feature flag.
|
*/
'legacy_model_binding' => false,
/*
|---------------------------------------------------------------------------
| Auto-inject Frontend Assets
|---------------------------------------------------------------------------
|
| By default, Livewire automatically injects its JavaScript and CSS into the
| <head> and <body> of pages containing Livewire components. By disabling
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
*/
'inject_assets' => true,
/*
|---------------------------------------------------------------------------
| Navigate (SPA mode)
|---------------------------------------------------------------------------
|
| By adding `wire:navigate` to links in your Livewire application, Livewire
| will prevent the default link handling and instead request those pages
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
*/
'navigate' => [
'show_progress_bar' => true,
'progress_bar_color' => '#2299dd',
],
/*
|---------------------------------------------------------------------------
| HTML Morph Markers
|---------------------------------------------------------------------------
|
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
| after each update. To make this process more reliable, Livewire injects
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
*/
'inject_morph_markers' => true,
/*
|---------------------------------------------------------------------------
| Smart Wire Keys
|---------------------------------------------------------------------------
|
| Livewire uses loops and keys used within loops to generate smart keys that
| are applied to nested components that don't have them. This makes using
| nested components more reliable by ensuring that they all have keys.
|
*/
'smart_wire_keys' => false,
/*
|---------------------------------------------------------------------------
| Pagination Theme
|---------------------------------------------------------------------------
|
| When enabling Livewire's pagination feature by using the `WithPagination`
| trait, Livewire will use Tailwind templates to render pagination views
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
*/
'pagination_theme' => 'tailwind',
/*
|---------------------------------------------------------------------------
| Release Token
|---------------------------------------------------------------------------
|
| This token is stored client-side and sent along with each request to check
| a users session to see if a new release has invalidated it. If there is
| a mismatch it will throw an error and prompt for a browser refresh.
|
*/
'release_token' => 'a',
];
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
{"/livewire.js":"61e33937"}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Livewire;
use Livewire\Features\SupportAttributes\Attribute as BaseAttribute;
abstract class Attribute extends BaseAttribute
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportComputed\BaseComputed;
#[\Attribute]
class Computed extends BaseComputed
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportIsolating\BaseIsolate;
#[\Attribute]
class Isolate extends BaseIsolate
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportJsEvaluation\BaseJs;
#[\Attribute]
class Js extends BaseJs
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportPageComponents\BaseLayout;
#[\Attribute]
class Layout extends BaseLayout
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportLazyLoading\BaseLazy;
#[\Attribute]
class Lazy extends BaseLazy
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportLockedProperties\BaseLocked;
#[\Attribute]
class Locked extends BaseLocked
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportWireModelingNestedComponents\BaseModelable;
#[\Attribute]
class Modelable extends BaseModelable
{
//
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace Livewire\Attributes;
use Attribute;
use Livewire\Features\SupportEvents\BaseOn;
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class On extends BaseOn
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportReactiveProps\BaseReactive;
#[\Attribute]
class Reactive extends BaseReactive
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Mechanisms\HandleComponents\BaseRenderless;
#[\Attribute]
class Renderless extends BaseRenderless
{
//
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Attributes;
use Attribute;
use Livewire\Features\SupportValidation\BaseRule;
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
class Rule extends BaseRule
{
//
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace Livewire\Attributes;
use Attribute;
use Livewire\Features\SupportSession\BaseSession;
#[Attribute(Attribute::TARGET_PROPERTY)]
class Session extends BaseSession
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportPageComponents\BaseTitle;
#[\Attribute]
class Title extends BaseTitle
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Attributes;
use Livewire\Features\SupportQueryString\BaseUrl;
#[\Attribute]
class Url extends BaseUrl
{
//
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Attributes;
use Attribute;
use Livewire\Features\SupportValidation\BaseValidate;
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
class Validate extends BaseValidate
{
//
}
+149
View File
@@ -0,0 +1,149 @@
<?php
namespace Livewire;
use Livewire\Features\SupportDisablingBackButtonCache\HandlesDisablingBackButtonCache;
use Livewire\Features\SupportPageComponents\HandlesPageComponents;
use Livewire\Features\SupportReleaseTokens\HandlesReleaseTokens;
use Livewire\Features\SupportJsEvaluation\HandlesJsEvaluation;
use Livewire\Features\SupportAttributes\HandlesAttributes;
use Livewire\Features\SupportValidation\HandlesValidation;
use Livewire\Features\SupportStreaming\HandlesStreaming;
use Livewire\Features\SupportRedirects\HandlesRedirects;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Features\SupportEvents\HandlesEvents;
use Livewire\Exceptions\PropertyNotFoundException;
use Livewire\Concerns\InteractsWithProperties;
use Illuminate\Support\Traits\Macroable;
use BadMethodCallException;
use Livewire\Features\SupportFormObjects\HandlesFormObjects;
abstract class Component
{
use Macroable { __call as macroCall; }
use AuthorizesRequests;
use InteractsWithProperties;
use HandlesEvents;
use HandlesRedirects;
use HandlesStreaming;
use HandlesAttributes;
use HandlesValidation;
use HandlesFormObjects;
use HandlesJsEvaluation;
use HandlesReleaseTokens;
use HandlesPageComponents;
use HandlesDisablingBackButtonCache;
protected $__id;
protected $__name;
function id()
{
return $this->getId();
}
function setId($id)
{
$this->__id = $id;
}
function getId()
{
return $this->__id;
}
function setName($name)
{
$this->__name = $name;
}
function getName()
{
return $this->__name;
}
function skipRender($html = null)
{
store($this)->set('skipRender', $html ?: true);
}
function skipMount()
{
store($this)->set('skipMount', true);
}
function skipHydrate()
{
store($this)->set('skipHydrate', true);
}
function __isset($property)
{
try {
$value = $this->__get($property);
if (isset($value)) {
return true;
}
} catch(PropertyNotFoundException $ex) {}
return false;
}
function __get($property)
{
$value = 'noneset';
$returnValue = function ($newValue) use (&$value) {
$value = $newValue;
};
$finish = trigger('__get', $this, $property, $returnValue);
$value = $finish($value);
if ($value === 'noneset') {
throw new PropertyNotFoundException($property, $this->getName());
}
return $value;
}
function __unset($property)
{
trigger('__unset', $this, $property);
}
function __call($method, $params)
{
$value = 'noneset';
$returnValue = function ($newValue) use (&$value) {
$value = $newValue;
};
$finish = trigger('__call', $this, $method, $params, $returnValue);
$value = $finish($value);
if ($value !== 'noneset') {
return $value;
}
if (static::hasMacro($method)) {
return $this->macroCall($method, $params);
}
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
public function tap($callback)
{
$callback($this);
return $this;
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Livewire;
abstract class ComponentHook
{
protected $component;
function setComponent($component)
{
$this->component = $component;
}
function callBoot(...$params) {
if (method_exists($this, 'boot')) $this->boot(...$params);
}
function callMount(...$params) {
if (method_exists($this, 'mount')) $this->mount(...$params);
}
function callHydrate(...$params) {
if (method_exists($this, 'hydrate')) $this->hydrate(...$params);
}
function callUpdate($propertyName, $fullPath, $newValue) {
$callbacks = [];
if (method_exists($this, 'update')) $callbacks[] = $this->update($propertyName, $fullPath, $newValue);
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) $callback(...$params);
}
};
}
function callCall($method, $params, $returnEarly) {
$callbacks = [];
if (method_exists($this, 'call')) $callbacks[] = $this->call($method, $params, $returnEarly);
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) $callback(...$params);
}
};
}
function callRender(...$params) {
$callbacks = [];
if (method_exists($this, 'render')) $callbacks[] = $this->render(...$params);
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) $callback(...$params);
}
};
}
function callDehydrate(...$params) {
if (method_exists($this, 'dehydrate')) $this->dehydrate(...$params);
}
function callDestroy(...$params) {
if (method_exists($this, 'destroy')) $this->destroy(...$params);
}
function callException(...$params) {
if (method_exists($this, 'exception')) $this->exception(...$params);
}
function getProperties()
{
return $this->component->all();
}
function getProperty($name)
{
return data_get($this->getProperties(), $name);
}
function storeSet($key, $value)
{
store($this->component)->set($key, $value);
}
function storePush($key, $value, $iKey = null)
{
store($this->component)->push($key, $value, $iKey);
}
function storeGet($key, $default = null)
{
return store($this->component)->get($key, $default);
}
function storeHas($key)
{
return store($this->component)->has($key);
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
namespace Livewire;
use WeakMap;
use Livewire\Drawer\Utils;
class ComponentHookRegistry
{
protected static $components;
protected static $componentHooks = [];
static function register($hook)
{
if (method_exists($hook, 'provide')) $hook::provide();
if (in_array($hook, static::$componentHooks)) return;
static::$componentHooks[] = $hook;
}
static function getHook($component, $hook)
{
if (! isset(static::$components[$component])) return;
$componentHooks = static::$components[$component];
foreach ($componentHooks as $componentHook) {
if ($componentHook instanceof $hook) return $componentHook;
}
}
static function boot()
{
static::$components = new WeakMap;
foreach (static::$componentHooks as $hook) {
on('mount', function ($component, $params, $key, $parent) use ($hook) {
if (! $hook = static::initializeHook($hook, $component)) {
return;
}
$hook->callBoot();
$hook->callMount($params, $parent);
});
on('hydrate', function ($component, $memo) use ($hook) {
if (! $hook = static::initializeHook($hook, $component)) {
return;
}
$hook->callBoot();
$hook->callHydrate($memo);
});
}
on('update', function ($component, $fullPath, $newValue) {
$propertyName = Utils::beforeFirstDot($fullPath);
return static::proxyCallToHooks($component, 'callUpdate')($propertyName, $fullPath, $newValue);
});
on('call', function ($component, $method, $params, $addEffect, $earlyReturn) {
return static::proxyCallToHooks($component, 'callCall')($method, $params, $earlyReturn);
});
on('render', function ($component, $view, $data) {
return static::proxyCallToHooks($component, 'callRender')($view, $data);
});
on('dehydrate', function ($component, $context) {
static::proxyCallToHooks($component, 'callDehydrate')($context);
});
on('destroy', function ($component, $context) {
static::proxyCallToHooks($component, 'callDestroy')($context);
});
on('exception', function ($target, $e, $stopPropagation) {
if ($target instanceof \Livewire\Component) {
static::proxyCallToHooks($target, 'callException')($e, $stopPropagation);
}
});
}
static public function initializeHook($hook, $target)
{
if (! isset(static::$components[$target])) static::$components[$target] = [];
$hook = new $hook;
$hook->setComponent($target);
// If no `skip` method has been implemented, then boot the hook anyway
if (method_exists($hook, 'skip') && $hook->skip()) {
return;
}
static::$components[$target][] = $hook;
return $hook;
}
static function proxyCallToHooks($target, $method) {
return function (...$params) use ($target, $method) {
$callbacks = [];
foreach (static::$components[$target] ?? [] as $hook) {
$callbacks[] = $hook->{$method}(...$params);
}
return function (...$forwards) use ($callbacks) {
foreach ($callbacks as $callback) {
$callback(...$forwards);
}
};
};
}
}
@@ -0,0 +1,144 @@
<?php
namespace Livewire\Concerns;
use Illuminate\Database\Eloquent\Model;
use Livewire\Drawer\Utils;
use Livewire\Form;
trait InteractsWithProperties
{
public function hasProperty($prop)
{
return property_exists($this, Utils::beforeFirstDot($prop));
}
public function getPropertyValue($name)
{
$value = $this->{Utils::beforeFirstDot($name)};
if (Utils::containsDots($name)) {
return data_get($value, Utils::afterFirstDot($name));
}
return $value;
}
public function fill($values)
{
$publicProperties = array_keys($this->all());
if ($values instanceof Model) {
$values = $values->toArray();
}
foreach ($values as $key => $value) {
if (in_array(Utils::beforeFirstDot($key), $publicProperties)) {
data_set($this, $key, $value);
}
}
}
public function reset(...$properties)
{
$properties = count($properties) && is_array($properties[0])
? $properties[0]
: $properties;
// Reset all
if (empty($properties)) {
$properties = array_keys($this->all());
}
$freshInstance = new static;
foreach ($properties as $property) {
$property = str($property);
// Check if the property contains a dot which means it is actually on a nested object like a FormObject
if (str($property)->contains('.')) {
$propertyName = $property->afterLast('.');
$objectName = $property->before('.');
// form object reset
if (is_subclass_of($this->{$objectName}, Form::class)) {
$this->{$objectName}->reset($propertyName);
continue;
}
$object = data_get($freshInstance, $objectName, null);
if (is_object($object)) {
$isInitialized = (new \ReflectionProperty($object, (string) $propertyName))->isInitialized($object);
} else {
$isInitialized = false;
}
} else {
$isInitialized = (new \ReflectionProperty($freshInstance, (string) $property))->isInitialized($freshInstance);
}
// Handle resetting properties that are not initialized by default.
if (! $isInitialized) {
data_forget($this, (string) $property);
continue;
}
data_set($this, $property, data_get($freshInstance, $property));
}
}
protected function resetExcept(...$properties)
{
if (count($properties) && is_array($properties[0])) {
$properties = $properties[0];
}
$keysToReset = array_diff(array_keys($this->all()), $properties);
if($keysToReset === []) {
return;
}
$this->reset($keysToReset);
}
public function pull($properties = null)
{
$wantsASingleValue = is_string($properties);
$properties = is_array($properties) ? $properties : func_get_args();
$beforeReset = match (true) {
empty($properties) => $this->all(),
$wantsASingleValue => $this->getPropertyValue($properties[0]),
default => $this->only($properties),
};
$this->reset($properties);
return $beforeReset;
}
public function only($properties)
{
$results = [];
foreach (is_array($properties) ? $properties : func_get_args() as $property) {
$results[$property] = $this->hasProperty($property) ? $this->getPropertyValue($property) : null;
}
return $results;
}
public function except($properties)
{
$properties = is_array($properties) ? $properties : func_get_args();
return array_diff_key($this->all(), array_flip($properties));
}
public function all()
{
return Utils::getPublicPropertiesDefinedOnSubclass($this);
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace Livewire\Drawer;
class BaseUtils
{
static function isSyntheticTuple($payload) {
return is_array($payload)
&& count($payload) === 2
&& isset($payload[1]['s']);
}
static function isAPrimitive($target) {
return
is_numeric($target) ||
is_string($target) ||
is_bool($target) ||
is_null($target);
}
static function getPublicPropertiesDefinedOnSubclass($target) {
return static::getPublicProperties($target, function ($property) {
// Filter out any properties from the first-party Component class...
return $property->getDeclaringClass()->getName() !== \Livewire\Component::class && $property->getDeclaringClass()->getName() !== \Livewire\Volt\Component::class;
});
}
static function getPublicProperties($target, $filter = null)
{
return collect((new \ReflectionObject($target))->getProperties())
->filter(function ($property) {
return $property->isPublic() && ! $property->isStatic() && $property->isDefault();
})
->filter($filter ?? fn () => true)
->mapWithKeys(function ($property) use ($target) {
// Ensures typed property is initialized in PHP >=7.4, if so, return its value,
// if not initialized, return null (as expected in earlier PHP Versions)
if (method_exists($property, 'isInitialized') && !$property->isInitialized($target)) {
// If a type of `array` is given with no value, let's assume users want
// it prefilled with an empty array...
$value = (method_exists($property, 'getType') && $property->getType() && method_exists($property->getType(), 'getName') && $property->getType()->getName() === 'array')
? [] : null;
} else {
$value = $property->getValue($target);
}
return [$property->getName() => $value];
})
->all();
}
static function getPublicMethodsDefinedBySubClass($target)
{
$methods = array_filter((new \ReflectionObject($target))->getMethods(), function ($method) {
$isInBaseComponentClass = $method->getDeclaringClass()->getName() === \Livewire\Component::class || $method->getDeclaringClass()->getName() === \Livewire\Volt\Component::class;
return $method->isPublic()
&& ! $method->isStatic()
&& ! $isInBaseComponentClass;
});
return array_map(function ($method) {
return $method->getName();
}, $methods);
}
static function hasAttribute($target, $property, $attributeClass) {
$property = static::getProperty($target, $property);
foreach ($property->getAttributes() as $attribute) {
$instance = $attribute->newInstance();
if ($instance instanceof $attributeClass) return true;
}
return false;
}
static function getProperty($target, $property) {
return (new \ReflectionObject($target))->getProperty($property);
}
static function propertyIsTyped($target, $property) {
$property = static::getProperty($target, $property);
return $property->hasType();
}
static function propertyIsTypedAndUninitialized($target, $property) {
$property = static::getProperty($target, $property);
return $property->hasType() && (! $property->isInitialized($target));
}
}
@@ -0,0 +1,148 @@
<?php
namespace Livewire\Drawer;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use BackedEnum;
use ReflectionClass;
use ReflectionMethod;
use Livewire\Component;
use Illuminate\Support\Reflector;
use Illuminate\Support\Collection;
use Illuminate\Routing\Route;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Contracts\Routing\UrlRoutable;
/**
* This class mirrors the functionality of Laravel's Illuminate\Routing\ImplicitRouteBinding class.
*/
class ImplicitRouteBinding
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function resolveAllParameters(Route $route, Component $component)
{
$params = $this->resolveMountParameters($route, $component);
$props = $this->resolveComponentProps($route, $component);
return $params->merge($props)->all();
}
public function resolveMountParameters(Route $route, Component $component)
{
if (! method_exists($component, 'mount')) {
return new Collection();
}
// Cache the current route action (this callback actually), just to be safe.
$cache = $route->getAction();
// We'll set the route action to be the "mount" method from the chosen
// Livewire component, to get the proper implicit bindings.
$route->uses(get_class($component).'@mount');
try {
// This is normally handled in the "SubstituteBindings" middleware, but
// because that middleware has already ran, we need to run them again.
$this->container['router']->substituteImplicitBindings($route);
$parameters = $route->resolveMethodDependencies($route->parameters(), new ReflectionMethod($component, 'mount'));
// Restore the original route action...
$route->setAction($cache);
} catch(\Exception $e) {
// Restore the original route action before an exception is thrown...
$route->setAction($cache);
throw $e;
}
return new Collection($parameters);
}
public function resolveComponentProps(Route $route, Component $component)
{
return $this->getPublicPropertyTypes($component)
->intersectByKeys($route->parametersWithoutNulls())
->map(function ($className, $propName) use ($route) {
// If typed public property, resolve the class
if ($className) {
$resolved = $this->resolveParameter($route, $propName, $className);
// We'll also pass the resolved model back to the route
// so that it can be used for any depending on bindings
$route->setParameter($propName, $resolved);
return $resolved;
}
// Otherwise, just return the route parameter
return $route->parameter($propName);
});
}
public function getPublicPropertyTypes($component)
{
return collect(Utils::getPublicPropertiesDefinedOnSubclass($component))
->map(function ($value, $name) use ($component) {
return Reflector::getParameterClassName(new \ReflectionProperty($component, $name));
});
}
protected function resolveParameter($route, $parameterName, $parameterClassName)
{
$parameterValue = $route->parameter($parameterName);
if ($parameterValue instanceof UrlRoutable) {
return $parameterValue;
}
if($enumValue = $this->resolveEnumParameter($parameterValue, $parameterClassName)) {
return $enumValue;
}
$instance = $this->container->make($parameterClassName);
$parent = $route->parentOfParameter($parameterName);
if ($parent instanceof UrlRoutable && ($route->enforcesScopedBindings() || array_key_exists($parameterName, $route->bindingFields()))) {
$model = $parent->resolveChildRouteBinding($parameterName, $parameterValue, $route->bindingFieldFor($parameterName));
} else {
if ($route->allowsTrashedBindings()) {
$model = $instance->resolveSoftDeletableRouteBinding($parameterValue, $route->bindingFieldFor($parameterName));
} else {
$model = $instance->resolveRouteBinding($parameterValue, $route->bindingFieldFor($parameterName));
}
}
if (! $model) {
throw (new ModelNotFoundException())->setModel(get_class($instance), [$parameterValue]);
}
return $model;
}
protected function resolveEnumParameter($parameterValue, $parameterClassName)
{
if ($parameterValue instanceof BackedEnum) {
return $parameterValue;
}
if ((new ReflectionClass($parameterClassName))->isEnum()) {
$enumValue = $parameterClassName::tryFrom($parameterValue);
if (is_null($enumValue)) {
throw new BackedEnumCaseNotFoundException($parameterClassName, $parameterValue);
}
return $enumValue;
}
return null;
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace Livewire\Drawer;
class Regexes
{
static $livewireOpeningTag = "
<
\s*
livewire[-\:]([\w\-\:\.]*)
(?<attributes>
(?:
\s+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
(?<![\/=\-])
>
";
static $livewireOpeningTagOrSelfClosingTag = "
<
\s*
livewire[-\:]([\w\-\:\.]*)
(?<attributes>
(?:
\s+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[:][$][\w]+
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
\/?>
";
static $livewireSelfClosingTag = "
<
\s*
livewire[-\:]([\w\-\:\.]*)
\s*
(?<attributes>
(?:
\s+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
\/>
";
static $livewireClosingTag = '<\/\s*livewire[-\:][\w\-\:\.]*\s*>';
static $slotOpeningTag = "
<
\s*
x[\-\:]slot
(?:\:(?<inlineName>\w+(?:-\w+)*))?
(?:\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))?
(?<attributes>
(?:
\s+
(?:
(?:
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
(?<![\/=\-])
>
";
static $slotClosingTag = '<\/\s*x[\-\:]slot[^>]*>';
static $bladeDirective = "\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?";
static function specificBladeDirective($directive) {
return "(@?$directive(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))";
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace Livewire\Drawer;
use Illuminate\Http\Request;
use Livewire\Exceptions\RootTagMissingFromViewException;
use Livewire\Features\SupportFileUploads\FileUploadConfiguration;
use function Livewire\invade;
class Utils extends BaseUtils
{
static function insertAttributesIntoHtmlRoot($html, $attributes) {
$attributesFormattedForHtmlElement = static::stringifyHtmlAttributes($attributes);
preg_match('/(?:\n\s*|^\s*)<([a-zA-Z0-9\-]+)/', $html, $matches, PREG_OFFSET_CAPTURE);
throw_unless(
count($matches),
new RootTagMissingFromViewException
);
$tagName = $matches[1][0];
$lengthOfTagName = strlen($tagName);
$positionOfFirstCharacterInTagName = $matches[1][1];
return substr_replace(
$html,
' '.$attributesFormattedForHtmlElement,
$positionOfFirstCharacterInTagName + $lengthOfTagName,
0
);
}
static function stringifyHtmlAttributes($attributes)
{
return collect($attributes)
->mapWithKeys(function ($value, $key) {
return [$key => static::escapeStringForHtml($value)];
})->map(function ($value, $key) {
return sprintf('%s="%s"', $key, $value);
})->implode(' ');
}
static function escapeStringForHtml($subject)
{
if (is_string($subject) || is_numeric($subject)) {
return htmlspecialchars($subject, ENT_QUOTES|ENT_SUBSTITUTE);
}
return htmlspecialchars(json_encode($subject), ENT_QUOTES|ENT_SUBSTITUTE);
}
static function pretendResponseIsFile($file, $contentType = 'application/javascript; charset=utf-8')
{
$lastModified = filemtime($file);
return static::cachedFileResponse($file, $contentType, $lastModified,
fn ($headers) => response()->file($file, $headers));
}
static function pretendPreviewResponseIsPreviewFile($filename)
{
$file = FileUploadConfiguration::path($filename);
$storage = FileUploadConfiguration::storage();
$mimeType = FileUploadConfiguration::mimeType($filename);
$lastModified = FileUploadConfiguration::lastModified($file);
return self::cachedFileResponse($filename, $mimeType, $lastModified,
fn ($headers) => $storage->download($file, $filename, $headers));
}
static private function cachedFileResponse($filename, $contentType, $lastModified, $downloadCallback)
{
$expires = strtotime('+1 year');
$cacheControl = 'public, max-age=31536000';
if (static::matchesCache($lastModified)) {
return response('', 304, [
'Expires' => static::httpDate($expires),
'Cache-Control' => $cacheControl,
]);
}
$headers = [
'Content-Type' => $contentType,
'Expires' => static::httpDate($expires),
'Cache-Control' => $cacheControl,
'Last-Modified' => static::httpDate($lastModified),
];
if (str($filename)->endsWith('.br')) {
$headers['Content-Encoding'] = 'br';
}
return $downloadCallback($headers);
}
static function matchesCache($lastModified)
{
$ifModifiedSince = app(Request::class)->header('if-modified-since');
return $ifModifiedSince !== null && @strtotime($ifModifiedSince) === $lastModified;
}
static function httpDate($timestamp)
{
return sprintf('%s GMT', gmdate('D, d M Y H:i:s', $timestamp));
}
static function containsDots($subject)
{
return str_contains($subject, '.');
}
static function dotSegments($subject)
{
return explode('.', $subject);
}
static function beforeFirstDot($subject)
{
return head(explode('.', $subject));
}
static function afterFirstDot($subject) : string
{
return str($subject)->after('.');
}
static public function hasProperty($target, $property)
{
return property_exists($target, static::beforeFirstDot($property));
}
static public function shareWithViews($name, $value)
{
$old = app('view')->shared($name, 'notfound');
app('view')->share($name, $value);
return $revert = function () use ($name, $old) {
if ($old === 'notfound') {
unset(invade(app('view'))->shared[$name]);
} else {
app('view')->share($name, $old);
}
};
}
static function generateBladeView($subject, $data = [])
{
if (! is_string($subject)) {
return tap($subject)->with($data);
}
$component = new class($subject) extends \Illuminate\View\Component
{
protected $template;
public function __construct($template)
{
$this->template = $template;
}
public function render()
{
return $this->template;
}
};
$view = app('view')->make($component->resolveView(), $data);
return $view;
}
static function applyMiddleware(\Illuminate\Http\Request $request, $middleware = [])
{
$response = (new \Illuminate\Pipeline\Pipeline(app()))
->send($request)
->through($middleware)
->then(function() {
return new \Illuminate\Http\Response();
});
if ($response instanceof \Illuminate\Http\RedirectResponse) {
abort($response);
}
return $response;
}
static function extractAttributeDataFromHtml($html, $attribute)
{
$data = (string) str($html)->betweenFirst($attribute.'="', '"');
return json_decode(
htmlspecialchars_decode($data, ENT_QUOTES|ENT_SUBSTITUTE),
associative: true,
);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace Livewire;
class EventBus
{
protected $listeners = [];
protected $listenersAfter = [];
protected $listenersBefore = [];
function boot()
{
app()->singleton($this::class);
}
function on($name, $callback) {
if (! isset($this->listeners[$name])) $this->listeners[$name] = [];
$this->listeners[$name][] = $callback;
return fn() => $this->off($name, $callback);
}
function before($name, $callback) {
if (! isset($this->listenersBefore[$name])) $this->listenersBefore[$name] = [];
$this->listenersBefore[$name][] = $callback;
return fn() => $this->off($name, $callback);
}
function after($name, $callback) {
if (! isset($this->listenersAfter[$name])) $this->listenersAfter[$name] = [];
$this->listenersAfter[$name][] = $callback;
return fn() => $this->off($name, $callback);
}
function off($name, $callback) {
$index = array_search($callback, $this->listeners[$name] ?? []);
$indexAfter = array_search($callback, $this->listenersAfter[$name] ?? []);
$indexBefore = array_search($callback, $this->listenersBefore[$name] ?? []);
if ($index !== false) unset($this->listeners[$name][$index]);
elseif ($indexAfter !== false) unset($this->listenersAfter[$name][$indexAfter]);
elseif ($indexBefore !== false) unset($this->listenersBefore[$name][$indexBefore]);
}
function trigger($name, ...$params) {
$middlewares = [];
$listeners = array_merge(
($this->listenersBefore[$name] ?? []),
($this->listeners[$name] ?? []),
($this->listenersAfter[$name] ?? []),
);
foreach ($listeners as $callback) {
$result = $callback(...$params);
if ($result) {
$middlewares[] = $result;
}
}
return function (&$forward = null, ...$extras) use ($middlewares) {
foreach ($middlewares as $finisher) {
if ($finisher === null) continue;
$finisher = is_array($finisher) ? last($finisher) : $finisher;
$result = $finisher($forward, ...$extras);
// Only overwrite previous "forward" if something is returned from the callback.
$forward = $result ?? $forward;
}
return $forward;
};
}
}
@@ -0,0 +1,8 @@
<?php
namespace Livewire\Exceptions;
trait BypassViewHandler
{
//
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Exceptions;
class ComponentAttributeMissingOnDynamicComponentException extends \Exception
{
use BypassViewHandler;
public function __construct()
{
parent::__construct('Dynamic component tag is missing component attribute.');
}
}
@@ -0,0 +1,8 @@
<?php
namespace Livewire\Exceptions;
class ComponentNotFoundException extends \Exception
{
use BypassViewHandler;
}
@@ -0,0 +1,11 @@
<?php
namespace Livewire\Exceptions;
class EventHandlerDoesNotExist extends \Exception
{
public function __construct(public readonly string $eventName)
{
parent::__construct('Handler for event ' . $eventName . ' does not exist');
}
}
@@ -0,0 +1,17 @@
<?php
namespace Livewire\Exceptions;
use Symfony\Component\HttpKernel\Exception\HttpException;
class LivewireReleaseTokenMismatchException extends HttpException
{
public function __construct()
{
parent::__construct(
419,
"Livewire detected a release token mismatch. \n".
"This happens when a user's browser session has been invalidated by a new deployment."
);
}
}
@@ -0,0 +1,15 @@
<?php
namespace Livewire\Exceptions;
class MethodNotFoundException extends \Exception
{
use BypassViewHandler;
public function __construct($method)
{
parent::__construct(
"Unable to call component method. Public method [{$method}] not found on component"
);
}
}
@@ -0,0 +1,17 @@
<?php
namespace Livewire\Exceptions;
class MissingRulesException extends \Exception
{
use BypassViewHandler;
public function __construct($instance)
{
$class = $instance::class;
parent::__construct(
"Missing [\$rules/rules()] property/method on: [{$class}]."
);
}
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Exceptions;
class NonPublicComponentMethodCall extends \Exception
{
use BypassViewHandler;
public function __construct($method)
{
parent::__construct('Component method not found: ['.$method.']');
}
}
@@ -0,0 +1,15 @@
<?php
namespace Livewire\Exceptions;
class PropertyNotFoundException extends \Exception
{
use BypassViewHandler;
public function __construct($property, $component)
{
parent::__construct(
"Property [\${$property}] not found on component: [{$component}]"
);
}
}
@@ -0,0 +1,15 @@
<?php
namespace Livewire\Exceptions;
class PublicPropertyNotFoundException extends \Exception
{
use BypassViewHandler;
public function __construct($property, $component)
{
parent::__construct(
"Unable to set component data. Public property [\${$property}] not found on component: [{$component}]"
);
}
}
@@ -0,0 +1,16 @@
<?php
namespace Livewire\Exceptions;
class RootTagMissingFromViewException extends \Exception
{
use BypassViewHandler;
public function __construct()
{
parent::__construct(
'Livewire encountered a missing root tag when trying to render a ' .
"component. \n When rendering a Blade view, make sure it contains a root HTML tag."
);
}
}
@@ -0,0 +1,107 @@
<?php
namespace Livewire\Features\SupportAttributes;
use Livewire\Component;
abstract class Attribute
{
protected Component $component;
protected $subTarget;
protected $subName;
protected AttributeLevel $level;
protected $levelName;
function __boot($component, AttributeLevel $level, $name = null, $subName = null, $subTarget = null)
{
$this->component = $component;
$this->subName = $subName;
$this->subTarget = $subTarget;
$this->level = $level;
$this->levelName = $name;
}
function getComponent()
{
return $this->component;
}
function getSubTarget()
{
return $this->subTarget;
}
function getSubName()
{
return $this->subName;
}
function getLevel()
{
return $this->level;
}
function getName()
{
return $this->levelName;
}
function getValue()
{
if ($this->level !== AttributeLevel::PROPERTY) {
throw new \Exception('Can\'t get the value of a non-property attribute.');
}
return data_get($this->component->all(), $this->levelName);
}
function setValue($value, ?bool $nullable = false)
{
if ($this->level !== AttributeLevel::PROPERTY) {
throw new \Exception('Can\'t set the value of a non-property attribute.');
}
if ($enum = $this->tryingToSetStringOrIntegerToEnum($value)) {
if($nullable) {
$value = $enum::tryFrom($value);
}
else {
$value = $enum::from($value);
}
}
data_set($this->component, $this->levelName, $value);
}
protected function tryingToSetStringOrIntegerToEnum($subject)
{
if (! is_string($subject) && ! is_int($subject)) return;
$target = $this->subTarget ?? $this->component;
$name = $this->subName ?? $this->levelName;
$property = str($name)->before('.')->toString();
$reflection = new \ReflectionProperty($target, $property);
$type = $reflection->getType();
// If the type is available, display its name
if ($type instanceof \ReflectionNamedType) {
$name = $type->getName();
// If the type is a BackedEnum then return it's name
if (is_subclass_of($name, \BackedEnum::class)) {
return $name;
}
}
return false;
}
}
@@ -0,0 +1,55 @@
<?php
namespace Livewire\Features\SupportAttributes;
use Illuminate\Support\Collection;
use ReflectionAttribute;
use ReflectionObject;
class AttributeCollection extends Collection
{
static function fromComponent($component, $subTarget = null, $propertyNamePrefix = '')
{
$instance = new static;
$reflected = new ReflectionObject($subTarget ?? $component);
foreach (static::getClassAttributesRecursively($reflected) as $attribute) {
$instance->push(tap($attribute->newInstance(), function ($attribute) use ($component, $subTarget) {
$attribute->__boot($component, AttributeLevel::ROOT, null, null, $subTarget);
}));
}
foreach ($reflected->getMethods() as $method) {
foreach ($method->getAttributes(Attribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$instance->push(tap($attribute->newInstance(), function ($attribute) use ($component, $method, $propertyNamePrefix, $subTarget) {
$attribute->__boot($component, AttributeLevel::METHOD, $propertyNamePrefix . $method->getName(), $method->getName(), $subTarget);
}));
}
}
foreach ($reflected->getProperties() as $property) {
foreach ($property->getAttributes(Attribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$instance->push(tap($attribute->newInstance(), function ($attribute) use ($component, $property, $propertyNamePrefix, $subTarget) {
$attribute->__boot($component, AttributeLevel::PROPERTY, $propertyNamePrefix . $property->getName(), $property->getName(), $subTarget);
}));
}
}
return $instance;
}
protected static function getClassAttributesRecursively($reflected) {
$attributes = [];
while ($reflected) {
foreach ($reflected->getAttributes(Attribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$attributes[] = $attribute;
}
$reflected = $reflected->getParentClass();
}
return $attributes;
}
}
@@ -0,0 +1,10 @@
<?php
namespace Livewire\Features\SupportAttributes;
enum AttributeLevel
{
case ROOT;
case PROPERTY;
case METHOD;
}
@@ -0,0 +1,25 @@
<?php
namespace Livewire\Features\SupportAttributes;
trait HandlesAttributes
{
protected AttributeCollection $attributes;
function getAttributes()
{
return $this->attributes ??= AttributeCollection::fromComponent($this);
}
function setPropertyAttribute($property, $attribute)
{
$attribute->__boot($this, AttributeLevel::PROPERTY, $property);
$this->mergeOutsideAttributes(new AttributeCollection([$attribute]));
}
function mergeOutsideAttributes(AttributeCollection $attributes)
{
$this->attributes = $this->getAttributes()->concat($attributes);
}
}
@@ -0,0 +1,143 @@
<?php
namespace Livewire\Features\SupportAttributes;
use Livewire\Features\SupportAttributes\Attribute as LivewireAttribute;
use Livewire\ComponentHook;
class SupportAttributes extends ComponentHook
{
function boot(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'boot')) {
$attribute->boot(...$params);
}
});
}
function mount(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'mount')) {
$attribute->mount(...$params);
}
});
}
function hydrate(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'hydrate')) {
$attribute->hydrate(...$params);
}
});
}
function update($propertyName, $fullPath, $newValue)
{
$callbacks = $this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->filter(fn ($attr) => $attr->getLevel() === AttributeLevel::PROPERTY)
// Call "update" on the root property attribute even if it's a deep update...
->filter(fn ($attr) => str($fullPath)->startsWith($attr->getName() . '.') || $fullPath === $attr->getName())
->map(function ($attribute) use ($fullPath, $newValue) {
if (method_exists($attribute, 'update')) {
return $attribute->update($fullPath, $newValue);
}
});
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) $callback(...$params);
}
};
}
function call($method, $params, $returnEarly)
{
$callbacks = $this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->filter(fn ($attr) => $attr->getLevel() === AttributeLevel::METHOD)
->filter(fn ($attr) => $attr->getName() === $method)
->map(function ($attribute) use ($params, $returnEarly) {
if (method_exists($attribute, 'call')) {
return $attribute->call($params, $returnEarly);
}
});
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) $callback(...$params);
}
};
}
function render(...$params)
{
$callbacks = $this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->map(function ($attribute) use ($params) {
if (method_exists($attribute, 'render')) {
return $attribute->render(...$params);
}
});
return function (...$params) use ($callbacks) {
foreach ($callbacks as $callback) {
if (is_callable($callback)) {
$callback(...$params);
}
}
};
}
function dehydrate(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'dehydrate')) {
$attribute->dehydrate(...$params);
}
});
}
function destroy(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'destroy')) {
$attribute->destroy(...$params);
}
});
}
function exception(...$params)
{
$this->component
->getAttributes()
->whereInstanceOf(LivewireAttribute::class)
->each(function ($attribute) use ($params) {
if (method_exists($attribute, 'exception')) {
$attribute->exception(...$params);
}
});
}
}
@@ -0,0 +1,99 @@
<?php
namespace Livewire\Features\SupportAutoInjectedAssets;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Livewire\ComponentHook;
use Livewire\Features\SupportScriptsAndAssets\SupportScriptsAndAssets;
use Livewire\Mechanisms\FrontendAssets\FrontendAssets;
use function Livewire\on;
class SupportAutoInjectedAssets extends ComponentHook
{
static $hasRenderedAComponentThisRequest = false;
static $forceAssetInjection = false;
static function provide()
{
on('flush-state', function () {
static::$hasRenderedAComponentThisRequest = false;
static::$forceAssetInjection = false;
});
app('events')->listen(RequestHandled::class, function ($handled) {
// If this is a successful HTML response...
if (! str($handled->response->headers->get('content-type'))->contains('text/html')) return;
if (! method_exists($handled->response, 'status') || $handled->response->status() !== 200) return;
$assetsHead = '';
$assetsBody = '';
// If `@assets` has been used outside of a Livewire component then we need
// to process those assets to be injected alongside the other assets...
SupportScriptsAndAssets::processNonLivewireAssets();
$assets = array_values(SupportScriptsAndAssets::getAssets());
// If there are additional head assets, inject those...
if (count($assets) > 0) {
foreach ($assets as $asset) {
$assetsHead .= $asset."\n";
}
}
// If we're injecting Livewire assets...
if (static::shouldInjectLivewireAssets()) {
$assetsHead .= FrontendAssets::styles()."\n";
$assetsBody .= FrontendAssets::scripts()."\n";
}
if ($assetsHead === '' && $assetsBody === '') return;
$html = $handled->response->getContent();
if (str($html)->contains('</html>')) {
$originalContent = $handled->response->original;
$handled->response->setContent(static::injectAssets($html, $assetsHead, $assetsBody));
$handled->response->original = $originalContent;
}
});
}
protected static function shouldInjectLivewireAssets()
{
if (! static::$forceAssetInjection && config('livewire.inject_assets', true) === false) return false;
if ((! static::$hasRenderedAComponentThisRequest) && (! static::$forceAssetInjection)) return false;
if (app(FrontendAssets::class)->hasRenderedScripts) return false;
return true;
}
protected static function getLivewireAssets()
{
$livewireStyles = FrontendAssets::styles();
$livewireScripts = FrontendAssets::scripts();
}
public function dehydrate()
{
static::$hasRenderedAComponentThisRequest = true;
}
static function injectAssets($html, $assetsHead, $assetsBody)
{
$html = str($html);
if ($html->test('/<\s*\/\s*head\s*>/i') && $html->test('/<\s*\/\s*body\s*>/i')) {
return $html
->replaceMatches('/(<\s*\/\s*head\s*>)/i', $assetsHead.'$1')
->replaceMatches('/(<\s*\/\s*body\s*>)/i', $assetsBody.'$1')
->toString();
}
return $html
->replaceMatches('/(<\s*html(?:\s[^>])*>)/i', '$1'.$assetsHead)
->replaceMatches('/(<\s*\/\s*html\s*>)/i', $assetsBody.'$1')
->toString();
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportBladeAttributes;
use Livewire\WireDirective;
use Illuminate\View\ComponentAttributeBag;
use Livewire\ComponentHook;
class SupportBladeAttributes extends ComponentHook
{
static function provide()
{
ComponentAttributeBag::macro('wire', function ($name) {
$entries = head((array) $this->whereStartsWith('wire:'.$name));
$directive = head(array_keys($entries));
$value = head(array_values($entries));
return new WireDirective($name, $directive, $value);
});
}
}
@@ -0,0 +1,82 @@
<?php
namespace Livewire\Features\SupportChecksumErrorDebugging;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
class SupportChecksumErrorDebugging
{
function boot()
{
// @todo: dont write to this file unless the command is running...
return;
$file = storage_path('framework/cache/lw-checksum-log.json');
Artisan::command('livewire:monitor-checksum', function () use ($file) {
File::put($file, json_encode(['checksums' => [], 'failure' => null]));
$this->info('Monitoring for checksum errors...');
while (true) {
$cache = json_decode(File::get($file), true);
if ($cache['failure']) {
$this->info('Failure: '.$cache['failure']);
$cache['failure'] = null;
}
File::put($file, json_encode($cache));
sleep(1);
}
})->purpose('Debug checksum errors in Livewire');
on('checksum.fail', function ($checksum, $comparitor, $tamperedSnapshot) use ($file) {
$cache = json_decode(File::get($file), true);
if (! isset($cache['checksums'][$checksum])) return;
$canonicalSnapshot = $cache['checksums'][$checksum];
$good = $this->array_diff_assoc_recursive($canonicalSnapshot, $tamperedSnapshot);
$bad = $this->array_diff_assoc_recursive($tamperedSnapshot, $canonicalSnapshot);
$cache['failure'] = "\nBefore: ".json_encode($good)."\nAfter: ".json_encode($bad);
File::put($file, json_encode($cache));
});
on('checksum.generate', function ($checksum, $snapshot) use ($file) {
$cache = json_decode(File::get($file), true);
$cache['checksums'][$checksum] = $snapshot;
File::put($file, json_encode($cache));
});
}
// https://www.php.net/manual/en/function.array-diff-assoc.php#111675
function array_diff_assoc_recursive($array1, $array2) {
$difference=array();
foreach($array1 as $key => $value) {
if( is_array($value) ) {
if( !isset($array2[$key]) || !is_array($array2[$key]) ) {
$difference[$key] = $value;
} else {
$new_diff = $this->array_diff_assoc_recursive($value, $array2[$key]);
if( !empty($new_diff) )
$difference[$key] = $new_diff;
}
} else if( !array_key_exists($key,$array2) || $array2[$key] !== $value ) {
$difference[$key] = $value;
}
}
return $difference;
}
}
@@ -0,0 +1,141 @@
<?php
namespace Livewire\Features\SupportCompiledWireKeys;
use Illuminate\Support\Facades\Blade;
use Livewire\ComponentHook;
use function Livewire\on;
class SupportCompiledWireKeys extends ComponentHook
{
public static $loopStack = [];
public static $currentLoop = [
'count' => null,
'index' => null,
'key' => null,
];
public static function provide()
{
on('flush-state', function () {
static::$loopStack = [];
static::$currentLoop = [
'count' => null,
'index' => null,
'key' => null,
];
});
if (! config('livewire.smart_wire_keys', true)) {
return;
}
static::registerPrecompilers();
}
public static function registerPrecompilers()
{
Blade::precompiler(function ($contents) {
$contents = static::compile($contents);
return $contents;
});
}
public static function compile($contents)
{
// Strip out all livewire tag components as we don't want to match any of them...
$placeholder = '<__livewire-component-placeholder__>';
$cleanedContents = preg_replace('/<livewire:[^>]+?\/>/is', $placeholder, $contents);
// Handle `wire:key` attributes on elements...
preg_match_all('/(?<=\s)wire:key\s*=\s*(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/', $cleanedContents, $keys);
foreach ($keys[0] as $index => $key) {
$escapedKey = str_replace("'", "\'", $keys[1][$index]);
$prefix = "<?php \Livewire\Features\SupportCompiledWireKeys\SupportCompiledWireKeys::processElementKey('{$escapedKey}', get_defined_vars()); ?>";
$contents = str_replace($key, $prefix . $key, $contents);
}
// Handle `wire:key` attributes on Blade components...
$contents = preg_replace(
'/(<\?php\s+\$component->withAttributes\(\[.*?\]\);\s*\?>)/s',
"$1\n<?php \Livewire\Features\SupportCompiledWireKeys\SupportCompiledWireKeys::processComponentKey(\$component); ?>\n",
$contents
);
return $contents;
}
public static function openLoop() {
if (static::$currentLoop['count'] === null) {
static::$currentLoop['count'] = 0;
} else {
static::$currentLoop['count']++;
}
static::$loopStack[] = static::$currentLoop;
static::$currentLoop = [
'count' => null,
'index' => null,
'key' => null,
];
}
public static function startLoop($index) {
static::$currentLoop['index'] = $index;
}
public static function endLoop() {
static::$currentLoop = [
'count' => null,
'index' => null,
'key' => null,
];
}
public static function closeLoop() {
static::$currentLoop = array_pop(static::$loopStack);
}
public static function processElementKey($keyString, $data)
{
$key = Blade::render($keyString, $data);
static::$currentLoop['key'] = $key;
}
public static function processComponentKey($component)
{
if ($component->attributes->has('wire:key')) {
static::$currentLoop['key'] = $component->attributes->get('wire:key');
}
}
public static function generateKey($deterministicBladeKey, $key = null)
{
$finalKey = $deterministicBladeKey;
$loops = array_merge(static::$loopStack, [static::$currentLoop]);
foreach ($loops as $loop) {
if (isset($loop['key']) || isset($loop['index'])) {
$finalKey .= isset($loop['key'])
? '-' . $loop['key']
: '-' . $loop['index'];
}
if (isset($loop['count'])) {
$finalKey .= '-' . $loop['count'];
}
}
if (isset($key) && $key !== '') {
$finalKey .= '-' . $key;
}
return $finalKey;
}
}
@@ -0,0 +1,152 @@
<?php
namespace Livewire\Features\SupportComputed;
use function Livewire\invade;
use function Livewire\on;
use function Livewire\off;
use Livewire\Features\SupportAttributes\Attribute;
use Illuminate\Support\Facades\Cache;
#[\Attribute]
class BaseComputed extends Attribute
{
protected $requestCachedValue;
function __construct(
public $persist = false,
public $seconds = 3600, // 1 hour...
public $cache = false,
public $key = null,
public $tags = null,
) {}
function boot()
{
off('__get', $this->handleMagicGet(...));
on('__get', $this->handleMagicGet(...));
off('__unset', $this->handleMagicUnset(...));
on('__unset', $this->handleMagicUnset(...));
}
function call()
{
throw new CannotCallComputedDirectlyException(
$this->component->getName(),
$this->getName(),
);
}
protected function handleMagicGet($target, $property, $returnValue)
{
if ($target !== $this->component) return;
if ($this->generatePropertyName($property) !== $this->getName()) return;
if ($this->persist) {
$returnValue($this->handlePersistedGet());
return;
}
if ($this->cache) {
$returnValue($this->handleCachedGet());
return;
}
$returnValue(
$this->requestCachedValue ??= $this->evaluateComputed()
);
}
protected function handleMagicUnset($target, $property)
{
if ($target !== $this->component) return;
if ($property !== $this->getName()) return;
if ($this->persist) {
$this->handlePersistedUnset();
return;
}
if ($this->cache) {
$this->handleCachedUnset();
return;
}
unset($this->requestCachedValue);
}
protected function handlePersistedGet()
{
$key = $this->generatePersistedKey();
$closure = fn () => $this->evaluateComputed();
return match(Cache::supportsTags() && !empty($this->tags)) {
true => Cache::tags($this->tags)->remember($key, $this->seconds, $closure),
default => Cache::remember($key, $this->seconds, $closure)
};
}
protected function handleCachedGet()
{
$key = $this->generateCachedKey();
$closure = fn () => $this->evaluateComputed();
return match(Cache::supportsTags() && !empty($this->tags)) {
true => Cache::tags($this->tags)->remember($key, $this->seconds, $closure),
default => Cache::remember($key, $this->seconds, $closure)
};
}
protected function handlePersistedUnset()
{
$key = $this->generatePersistedKey();
Cache::forget($key);
}
protected function handleCachedUnset()
{
$key = $this->generateCachedKey();
Cache::forget($key);
}
protected function generatePersistedKey()
{
if ($this->key) return $this->key;
return 'lw_computed.'.$this->component->getId().'.'.$this->getName();
}
protected function generateCachedKey()
{
if ($this->key) return $this->key;
return 'lw_computed.'.$this->component->getName().'.'.$this->getName();
}
protected function evaluateComputed()
{
return invade($this->component)->{parent::getName()}();
}
public function getName()
{
return $this->generatePropertyName(parent::getName());
}
private function generatePropertyName($value)
{
return str($value)->camel()->toString();
}
}
@@ -0,0 +1,15 @@
<?php
namespace Livewire\Features\SupportComputed;
use Exception;
class CannotCallComputedDirectlyException extends Exception
{
function __construct($componentName, $methodName)
{
parent::__construct(
"Cannot call [{$methodName}()] computed property method directly on component: {$componentName}"
);
}
}
@@ -0,0 +1,74 @@
<?php
namespace Livewire\Features\SupportComputed;
use Livewire\ComponentHook;
use Livewire\Drawer\Utils as SyntheticUtils;
use function Livewire\on;
use function Livewire\store;
use function Livewire\wrap;
class SupportLegacyComputedPropertySyntax extends ComponentHook
{
static function provide()
{
on('__get', function ($target, $property, $returnValue) {
if (static::hasComputedProperty($target, $property)) {
$returnValue(static::getComputedProperty($target, $property));
}
});
on('__unset', function ($target, $property) {
if (static::hasComputedProperty($target, $property)) {
store($target)->unset('computedProperties', $property);
}
});
}
public static function getComputedProperties($target)
{
return collect(static::getComputedPropertyNames($target))
->mapWithKeys(function ($property) use ($target) {
return [$property => static::getComputedProperty($target, $property)];
})
->all();
}
public static function hasComputedProperty($target, $property)
{
return array_search((string) str($property)->camel(), static::getComputedPropertyNames($target)) !== false;
}
public static function getComputedProperty($target, $property)
{
if (! static::hasComputedProperty($target, $property)) {
throw new \Exception('No computed property found: $'.$property);
}
$method = 'get'.str($property)->studly().'Property';
store($target)->push(
'computedProperties',
$value = store($target)->find('computedProperties', $property, fn() => wrap($target)->$method()),
$property,
);
return $value;
}
public static function getComputedPropertyNames($target)
{
$methodNames = SyntheticUtils::getPublicMethodsDefinedBySubClass($target);
return collect($methodNames)
->filter(function ($method) {
return str($method)->startsWith('get')
&& str($method)->endsWith('Property');
})
->map(function ($method) {
return (string) str($method)->between('get', 'Property')->camel();
})
->all();
}
}
@@ -0,0 +1,57 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:attribute')]
class AttributeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'livewire:attribute {name} {--force}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Livewire attribute class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Attribute';
/**
* Get the stub file for the generator.
*
* @return string
*/
public function getStub()
{
if (File::exists(base_path('stubs/livewire.attribute.stub'))) {
return base_path('stubs/livewire.attribute.stub');
}
return __DIR__ . DIRECTORY_SEPARATOR . 'livewire.attribute.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
public function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . '\Livewire\Attributes';
}
}
@@ -0,0 +1,225 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
use function Livewire\str;
class ComponentParser
{
protected $baseClassNamespace;
protected $baseTestNamespace;
protected $baseClassPath;
protected $baseViewPath;
protected $baseTestPath;
protected $stubDirectory;
protected $viewPath;
protected $component;
protected $componentClass;
protected $directories;
public function __construct($classNamespace, $viewPath, $rawCommand, $stubSubDirectory = '')
{
$this->baseClassNamespace = $classNamespace;
$this->baseTestNamespace = 'Tests\Feature\Livewire';
$classPath = static::generatePathFromNamespace($classNamespace);
$testPath = static::generateTestPathFromNamespace($this->baseTestNamespace);
$this->baseClassPath = rtrim($classPath, DIRECTORY_SEPARATOR).'/';
$this->baseViewPath = rtrim($viewPath, DIRECTORY_SEPARATOR).'/';
$this->baseTestPath = rtrim($testPath, DIRECTORY_SEPARATOR).'/';
if(! empty($stubSubDirectory) && str($stubSubDirectory)->startsWith('..')) {
$this->stubDirectory = rtrim(str($stubSubDirectory)->replaceFirst('..' . DIRECTORY_SEPARATOR, ''), DIRECTORY_SEPARATOR).'/';
} else {
$this->stubDirectory = rtrim('stubs'.DIRECTORY_SEPARATOR.$stubSubDirectory, DIRECTORY_SEPARATOR).'/';
}
$directories = preg_split('/[.\/(\\\\)]+/', $rawCommand);
$camelCase = str(array_pop($directories))->camel();
$kebabCase = str($camelCase)->kebab();
$this->component = $kebabCase;
$this->componentClass = str($this->component)->studly();
$this->directories = array_map([Str::class, 'studly'], $directories);
}
public function component()
{
return $this->component;
}
public function classPath()
{
return $this->baseClassPath.collect()
->concat($this->directories)
->push($this->classFile())
->implode('/');
}
public function relativeClassPath() : string
{
return str($this->classPath())->replaceFirst(base_path().DIRECTORY_SEPARATOR, '');
}
public function classFile()
{
return $this->componentClass.'.php';
}
public function classNamespace()
{
return empty($this->directories)
? $this->baseClassNamespace
: $this->baseClassNamespace.'\\'.collect()
->concat($this->directories)
->map([Str::class, 'studly'])
->implode('\\');
}
public function className()
{
return $this->componentClass;
}
public function classContents($inline = false)
{
$stubName = $inline ? 'livewire.inline.stub' : 'livewire.stub';
if (File::exists($stubPath = base_path($this->stubDirectory.$stubName))) {
$template = file_get_contents($stubPath);
} else {
$template = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.$stubName);
}
if ($inline) {
$template = preg_replace('/\[quote\]/', $this->wisdomOfTheTao(), $template);
}
return preg_replace(
['/\[namespace\]/', '/\[class\]/', '/\[view\]/'],
[$this->classNamespace(), $this->className(), $this->viewName()],
$template
);
}
public function viewPath()
{
return $this->baseViewPath.collect()
->concat($this->directories)
->map([Str::class, 'kebab'])
->push($this->viewFile())
->implode(DIRECTORY_SEPARATOR);
}
public function relativeViewPath() : string
{
return str($this->viewPath())->replaceFirst(base_path().'/', '');
}
public function viewFile()
{
return $this->component.'.blade.php';
}
public function viewName()
{
return collect()
->when(config('livewire.view_path') !== resource_path(), function ($collection) {
return $collection->concat(explode('/',str($this->baseViewPath)->after(resource_path('views'))));
})
->filter()
->concat($this->directories)
->map([Str::class, 'kebab'])
->push($this->component)
->implode('.');
}
public function viewContents()
{
if( ! File::exists($stubPath = base_path($this->stubDirectory.'livewire.view.stub'))) {
$stubPath = __DIR__.DIRECTORY_SEPARATOR.'livewire.view.stub';
}
return preg_replace(
'/\[quote\]/',
$this->wisdomOfTheTao(),
file_get_contents($stubPath)
);
}
public function testNamespace()
{
return empty($this->directories)
? $this->baseTestNamespace
: $this->baseTestNamespace.'\\'.collect()
->concat($this->directories)
->map([Str::class, 'studly'])
->implode('\\');
}
public function testClassName()
{
return $this->componentClass.'Test';
}
public function testFile()
{
return $this->componentClass.'Test.php';
}
public function testPath()
{
return $this->baseTestPath.collect()
->concat($this->directories)
->push($this->testFile())
->implode('/');
}
public function relativeTestPath() : string
{
return str($this->testPath())->replaceFirst(base_path().'/', '');
}
public function testContents($testType = 'phpunit')
{
$stubName = $testType === 'pest' ? 'livewire.pest.stub' : 'livewire.test.stub';
if(File::exists($stubPath = base_path($this->stubDirectory.$stubName))) {
$template = file_get_contents($stubPath);
} else {
$template = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.$stubName);
}
return preg_replace(
['/\[testnamespace\]/', '/\[classwithnamespace\]/', '/\[testclass\]/', '/\[class\]/'],
[$this->testNamespace(), $this->classNamespace() . '\\' . $this->className(), $this->testClassName(), $this->className()],
$template
);
}
public function wisdomOfTheTao()
{
$wisdom = require __DIR__.DIRECTORY_SEPARATOR.'the-tao.php';
return Arr::random($wisdom);
}
public static function generatePathFromNamespace($namespace)
{
$name = str($namespace)->finish('\\')->replaceFirst(app()->getNamespace(), '');
return app('path').'/'.str_replace('\\', '/', $name);
}
public static function generateTestPathFromNamespace($namespace)
{
return base_path(str($namespace)
->replace('\\', '/', $namespace)
->replaceFirst('T', 't'));
}
}
@@ -0,0 +1,45 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class ComponentParserFromExistingComponent extends ComponentParser
{
protected $existingParser;
public function __construct($classNamespace, $viewPath, $rawCommand, $existingParser)
{
$this->existingParser = $existingParser;
parent::__construct($classNamespace, $viewPath, $rawCommand);
}
public function classContents($inline = false)
{
$originalFile = file_get_contents($this->existingParser->classPath());
$escapedClassNamespace = preg_replace('/\\\/', '\\\\\\', $this->existingParser->classNamespace());
return preg_replace_array(
["/namespace {$escapedClassNamespace}/", "/class {$this->existingParser->className()}/", "/{$this->existingParser->viewName()}/"],
["namespace {$this->classNamespace()}", "class {$this->className()}", $this->viewName()],
$originalFile
);
}
public function testContents($testType = 'phpunit')
{
$file_content = file_get_contents($this->existingParser->testPath());
$escapedTestNamespace = preg_replace('/\\\/', '\\\\\\', $this->existingParser->testNamespace());
$escapedClassWithNamespace = preg_replace('/\\\/', '\\\\\\', $this->existingParser->classNamespace() . '\\' . $this->existingParser->className());
$replaces = [
"/namespace {$escapedTestNamespace}/" => 'namespace ' . $this->testNamespace(),
"/use {$escapedClassWithNamespace}/" => 'use ' . $this->classNamespace() . '\\' . $this->className(),
"/class {$this->existingParser->testClassName()}/" => 'class ' . $this->testClassName(),
"/{$this->existingParser->className()}::class/" => $this->className() . '::class',
];
return preg_replace(array_keys($replaces), array_values($replaces), $file_content);
}
}
@@ -0,0 +1,86 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:copy')]
class CopyCommand extends FileManipulationCommand
{
protected $signature = 'livewire:copy {name} {new-name} {--inline} {--force} {--test}';
protected $description = 'Copy a Livewire component';
protected $newParser;
public function handle()
{
$this->parser = new ComponentParser(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('name')
);
$this->newParser = new ComponentParserFromExistingComponent(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('new-name'),
$this->parser
);
$force = $this->option('force');
$inline = $this->option('inline');
$test = $this->option('test');
$class = $this->copyClass($force, $inline);
if (! $inline) $view = $this->copyView($force);
if ($test){
$test = $this->copyTest($force);
}
$this->line("<options=bold,reverse;fg=green> COMPONENT COPIED </> 🤙\n");
$class && $this->line("<options=bold;fg=green>CLASS:</> {$this->parser->relativeClassPath()} <options=bold;fg=green>=></> {$this->newParser->relativeClassPath()}");
if (! $inline) $view && $this->line("<options=bold;fg=green>VIEW:</> {$this->parser->relativeViewPath()} <options=bold;fg=green>=></> {$this->newParser->relativeViewPath()}");
if ($test) $test && $this->line("<options=bold;fg=green>Test:</> {$this->parser->relativeTestPath()} <options=bold;fg=green>=></> {$this->newParser->relativeTestPath()}");
}
protected function copyTest($force)
{
if (File::exists($this->newParser->testPath()) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Test already exists:</> {$this->newParser->relativeTestPath()}");
return false;
}
$this->ensureDirectoryExists($this->newParser->testPath());
return File::copy("{$this->parser->testPath()}", $this->newParser->testPath());
}
protected function copyClass($force, $inline)
{
if (File::exists($this->newParser->classPath()) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Class already exists:</> {$this->newParser->relativeClassPath()}");
return false;
}
$this->ensureDirectoryExists($this->newParser->classPath());
return File::put($this->newParser->classPath(), $this->newParser->classContents($inline));
}
protected function copyView($force)
{
if (File::exists($this->newParser->viewPath()) && ! $force) {
$this->line("<fg=red;options=bold>View already exists:</> {$this->newParser->relativeViewPath()}");
return false;
}
$this->ensureDirectoryExists($this->newParser->viewPath());
return File::copy("{$this->parser->viewPath()}", $this->newParser->viewPath());
}
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class CpCommand extends CopyCommand
{
protected $signature = 'livewire:cp {name} {new-name} {--inline} {--force} {--test}';
protected function configure()
{
$this->setHidden(true);
}
}
@@ -0,0 +1,91 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:delete')]
class DeleteCommand extends FileManipulationCommand
{
protected $signature = 'livewire:delete {name} {--inline} {--force} {--test}';
protected $description = 'Delete a Livewire component';
public function handle()
{
$this->parser = new ComponentParser(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('name')
);
if (! $force = $this->option('force')) {
$shouldContinue = $this->confirm(
"<fg=yellow>Are you sure you want to delete the following files?</>\n\n{$this->parser->relativeClassPath()}\n{$this->parser->relativeViewPath()}\n"
);
if (! $shouldContinue) {
return;
}
}
$inline = $this->option('inline');
$test = $this->option('test');
$class = $this->removeClass($force);
if (! $inline) $view = $this->removeView($force);
if ($test) $test = $this->removeTest($force);
$this->line("<options=bold,reverse;fg=yellow> COMPONENT DESTROYED </> 🦖💫\n");
$class && $this->line("<options=bold;fg=yellow>CLASS:</> {$this->parser->relativeClassPath()}");
if (! $inline) $view && $this->line("<options=bold;fg=yellow>VIEW:</> {$this->parser->relativeViewPath()}");
if ($test) $test && $this->line("<options=bold;fg=yellow>Test:</> {$this->parser->relativeTestPath()}");
}
protected function removeTest($force = false)
{
$testPath = $this->parser->testPath();
if (! File::exists($testPath) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Test doesn't exist:</> {$this->parser->relativeTestPath()}");
return false;
}
File::delete($testPath);
return $testPath;
}
protected function removeClass($force = false)
{
$classPath = $this->parser->classPath();
if (! File::exists($classPath) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Class doesn't exist:</> {$this->parser->relativeClassPath()}");
return false;
}
File::delete($classPath);
return $classPath;
}
protected function removeView($force = false)
{
$viewPath = $this->parser->viewPath();
if (! File::exists($viewPath) && ! $force) {
$this->line("<fg=red;options=bold>View doesn't exist:</> {$this->parser->relativeViewPath()}");
return false;
}
File::delete($viewPath);
return $viewPath;
}
}
@@ -0,0 +1,45 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use function Livewire\str;
class FileManipulationCommand extends Command
{
protected $parser;
protected function ensureDirectoryExists($path)
{
if (! File::isDirectory(dirname($path))) {
File::makeDirectory(dirname($path), 0777, $recursive = true, $force = true);
}
}
public function isFirstTimeMakingAComponent()
{
$namespace = str(config('livewire.class_namespace'))->replaceFirst(app()->getNamespace(), '');
$livewireFolder = app_path($namespace->explode('\\')->implode(DIRECTORY_SEPARATOR));
return ! File::isDirectory($livewireFolder);
}
public function writeWelcomeMessage()
{
$asciiLogo = <<<EOT
<fg=magenta> _._</>
<fg=magenta>/ /<fg=white>o</>\ \ </> <fg=cyan> || () () __ </>
<fg=magenta>|_\ /_|</> <fg=cyan> || || \\\// /_\ \\\ // || |~~ /_\ </>
<fg=magenta> <fg=cyan>|</>`<fg=cyan>|</>`<fg=cyan>|</> </> <fg=cyan> || || \/ \\\_ \^/ || || \\\_ </>
EOT;
// _._
// / /o\ \ || () () __
// |_\ /_| || || \\\// /_\ \\\ // || |~~ /_\
// |`|`| || || \/ \\\_ \^/ || || \\\_
$this->line("\n".$asciiLogo."\n");
$this->line("\n<options=bold>Congratulations, you've created your first Livewire component!</> 🎉🎉🎉\n");
}
}
@@ -0,0 +1,57 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:form')]
class FormCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'livewire:form {name} {--force}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Livewire form class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Form';
/**
* Get the stub file for the generator.
*
* @return string
*/
public function getStub()
{
if (File::exists(base_path('stubs/livewire.form.stub'))) {
return base_path('stubs/livewire.form.stub');
}
return __DIR__ . DIRECTORY_SEPARATOR . 'livewire.form.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
public function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . '\Livewire\Forms';
}
}
@@ -0,0 +1,80 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:layout')]
class LayoutCommand extends FileManipulationCommand
{
protected $signature = 'livewire:layout {--force} {--stub= : If you have several stubs, stored in subfolders }';
protected $description = 'Create a new app layout file';
public function handle()
{
$baseViewPath = resource_path('views');
$layout = str(config('livewire.layout'));
$layoutPath = $this->layoutPath($baseViewPath, $layout);
$relativeLayoutPath = $this->relativeLayoutPath($layoutPath);
$force = $this->option('force');
$stubPath = $this->stubPath($this->option('stub'));
if (File::exists($layoutPath) && ! $force) {
$this->line("<fg=red;options=bold>View already exists:</> {$relativeLayoutPath}");
return false;
}
$this->ensureDirectoryExists($layoutPath);
$result = File::copy($stubPath, $layoutPath);
if ($result) {
$this->line("<options=bold,reverse;fg=green> LAYOUT CREATED </> 🤙\n");
$this->line("<options=bold;fg=green>CLASS:</> {$relativeLayoutPath}");
}
}
protected function stubPath($stubSubDirectory = '')
{
$stubName = 'livewire.layout.stub';
if (! empty($stubSubDirectory) && str($stubSubDirectory)->startsWith('..')) {
$stubDirectory = rtrim(str($stubSubDirectory)->replaceFirst('..' . DIRECTORY_SEPARATOR, ''), DIRECTORY_SEPARATOR) . '/';
} else {
$stubDirectory = rtrim('stubs' . DIRECTORY_SEPARATOR . $stubSubDirectory, DIRECTORY_SEPARATOR) . '/';
}
if (File::exists($stubPath = base_path($stubDirectory . $stubName))) {
return $stubPath;
}
return __DIR__ . DIRECTORY_SEPARATOR . $stubName;
}
protected function layoutPath($baseViewPath, $layout)
{
$directories = $layout->explode('.');
$name = Str::kebab($directories->pop());
return $baseViewPath . DIRECTORY_SEPARATOR . collect()
->concat($directories)
->map([Str::class, 'kebab'])
->push("{$name}.blade.php")
->implode(DIRECTORY_SEPARATOR);
}
protected function relativeLayoutPath($layoutPath)
{
return (string) str($layoutPath)->replaceFirst(base_path() . '/', '');
}
}
@@ -0,0 +1,257 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
#[AsCommand(name: 'livewire:make')]
class MakeCommand extends FileManipulationCommand implements PromptsForMissingInput
{
protected $signature = 'livewire:make {name} {--force} {--inline} {--test} {--pest} {--stub= : If you have several stubs, stored in subfolders }';
protected $description = 'Create a new Livewire component';
public function handle()
{
$this->parser = new ComponentParser(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('name'),
$this->option('stub')
);
if (!$this->isClassNameValid($name = $this->parser->className())) {
$this->line("<options=bold,reverse;fg=red> WHOOPS! </> 😳 \n");
$this->line("<fg=red;options=bold>Class is invalid:</> {$name}");
return;
}
if ($this->isReservedClassName($name)) {
$this->line("<options=bold,reverse;fg=red> WHOOPS! </> 😳 \n");
$this->line("<fg=red;options=bold>Class is reserved:</> {$name}");
return;
}
$force = $this->option('force');
$inline = $this->option('inline');
$test = $this->option('test') || $this->option('pest');
$testType = $this->option('pest') ? 'pest' : 'phpunit';
$showWelcomeMessage = $this->isFirstTimeMakingAComponent();
$class = $this->createClass($force, $inline);
$view = $this->createView($force, $inline);
if ($test) {
$test = $this->createTest($force, $testType);
}
if($class || $view) {
$this->line("<options=bold,reverse;fg=green> COMPONENT CREATED </> 🤙\n");
$class && $this->line("<options=bold;fg=green>CLASS:</> {$this->parser->relativeClassPath()}");
if (! $inline) {
$view && $this->line("<options=bold;fg=green>VIEW:</> {$this->parser->relativeViewPath()}");
}
if ($test) {
$test && $this->line("<options=bold;fg=green>TEST:</> {$this->parser->relativeTestPath()}");
}
if ($showWelcomeMessage && ! app()->runningUnitTests()) {
$this->writeWelcomeMessage();
}
}
}
protected function createClass($force = false, $inline = false)
{
$classPath = $this->parser->classPath();
if (File::exists($classPath) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Class already exists:</> {$this->parser->relativeClassPath()}");
return false;
}
$this->ensureDirectoryExists($classPath);
File::put($classPath, $this->parser->classContents($inline));
return $classPath;
}
protected function createView($force = false, $inline = false)
{
if ($inline) {
return false;
}
$viewPath = $this->parser->viewPath();
if (File::exists($viewPath) && ! $force) {
$this->line("<fg=red;options=bold>View already exists:</> {$this->parser->relativeViewPath()}");
return false;
}
$this->ensureDirectoryExists($viewPath);
File::put($viewPath, $this->parser->viewContents());
return $viewPath;
}
protected function createTest($force = false, $testType = 'phpunit')
{
$testPath = $this->parser->testPath();
if (File::exists($testPath) && ! $force) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Test class already exists:</> {$this->parser->relativeTestPath()}");
return false;
}
$this->ensureDirectoryExists($testPath);
File::put($testPath, $this->parser->testContents($testType));
return $testPath;
}
public function isClassNameValid($name)
{
return preg_match("/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/", $name);
}
public function isReservedClassName($name)
{
return array_search(strtolower($name), $this->getReservedName()) !== false;
}
protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output)
{
if ($this->didReceiveOptions($input)) {
return;
}
if(
confirm(
label: 'Do you want to make this an inline component?',
default: false
)
)
{
$input->setOption('inline', true);
}
if(
$testSuite = select(
label: 'Do you want to create a test file?',
options: [
false => 'No',
'phpunit' => 'PHPUnit',
'pest' => 'Pest',
],
)
)
{
$input->setOption('test', true);
if($testSuite === 'pest') {
$input->setOption('pest', true);
}
}
}
private function getReservedName()
{
return [
'parent',
'component',
'interface',
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'enum',
'eval',
'exit',
'extends',
'final',
'finally',
'fn',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'self',
'list',
'match',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'readonly',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'yield',
];
}
}
@@ -0,0 +1,8 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class MakeLivewireCommand extends MakeCommand
{
protected $signature = 'make:livewire {name} {--force} {--inline} {--test} {--pest} {--stub=}';
}
@@ -0,0 +1,95 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Support\Facades\File;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:move')]
class MoveCommand extends FileManipulationCommand
{
protected $signature = 'livewire:move {name} {new-name} {--force} {--inline}';
protected $description = 'Move a Livewire component';
protected $newParser;
public function handle()
{
$this->parser = new ComponentParser(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('name')
);
$this->newParser = new ComponentParserFromExistingComponent(
config('livewire.class_namespace'),
config('livewire.view_path'),
$this->argument('new-name'),
$this->parser
);
$inline = $this->option('inline');
$class = $this->renameClass();
if (! $inline) $view = $this->renameView();
$test = $this->renameTest();
if ($class) $this->line("<options=bold,reverse;fg=green> COMPONENT MOVED </> 🤙\n");
$class && $this->line("<options=bold;fg=green>CLASS:</> {$this->parser->relativeClassPath()} <options=bold;fg=green>=></> {$this->newParser->relativeClassPath()}");
if (! $inline) $view && $this->line("<options=bold;fg=green>VIEW:</> {$this->parser->relativeViewPath()} <options=bold;fg=green>=></> {$this->newParser->relativeViewPath()}");
if ($test) $test && $this->line("<options=bold;fg=green>Test:</> {$this->parser->relativeTestPath()} <options=bold;fg=green>=></> {$this->newParser->relativeTestPath()}");
}
protected function renameClass()
{
if (File::exists($this->newParser->classPath())) {
$this->line("<options=bold,reverse;fg=red> WHOOPS-IE-TOOTLES </> 😳 \n");
$this->line("<fg=red;options=bold>Class already exists:</> {$this->newParser->relativeClassPath()}");
return false;
}
$this->ensureDirectoryExists($this->newParser->classPath());
File::put($this->newParser->classPath(), $this->newParser->classContents());
return File::delete($this->parser->classPath());
}
protected function renameView()
{
$newViewPath = $this->newParser->viewPath();
if (File::exists($newViewPath)) {
$this->line("<fg=red;options=bold>View already exists:</> {$this->newParser->relativeViewPath()}");
return false;
}
$this->ensureDirectoryExists($newViewPath);
File::move($this->parser->viewPath(), $newViewPath);
return $newViewPath;
}
protected function renameTest()
{
$oldTestPath = $this->parser->testPath();
$newTestPath = $this->newParser->testPath();
if (! File::exists($oldTestPath) || File::exists($newTestPath)) {
return false;
}
$this->ensureDirectoryExists($newTestPath);
File::put($newTestPath, $this->newParser->testContents());
File::delete($oldTestPath);
return $newTestPath;
}
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class MvCommand extends MoveCommand
{
protected $signature = 'livewire:mv {name} {new-name} {--inline} {--force}';
protected function configure()
{
$this->setHidden(true);
}
}
@@ -0,0 +1,46 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:publish')]
class PublishCommand extends Command
{
protected $signature = 'livewire:publish
{ --assets : Indicates if Livewire\'s front-end assets should be published }
{ --config : Indicates if Livewire\'s config file should be published }
{ --pagination : Indicates if Livewire\'s pagination views should be published }';
protected $description = 'Publish Livewire configuration';
public function handle()
{
if ($this->option('assets')) {
$this->publishAssets();
} elseif ($this->option('config')) {
$this->publishConfig();
} elseif ($this->option('pagination')) {
$this->publishPagination();
} else {
$this->publishConfig();
$this->publishPagination();
}
}
public function publishAssets()
{
$this->call('vendor:publish', ['--tag' => 'livewire:assets', '--force' => true]);
}
public function publishConfig()
{
$this->call('vendor:publish', ['--tag' => 'livewire:config', '--force' => true]);
}
public function publishPagination()
{
$this->call('vendor:publish', ['--tag' => 'livewire:pagination', '--force' => true]);
}
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class RmCommand extends DeleteCommand
{
protected $signature = 'livewire:rm {name} {--inline} {--force} {--test}';
protected function configure()
{
$this->setHidden(true);
}
}
@@ -0,0 +1,98 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Aws\S3\S3Client;
use function array_merge;
use function Livewire\invade;
use Livewire\Features\SupportFileUploads\FileUploadConfiguration;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:configure-s3-upload-cleanup')]
class S3CleanupCommand extends Command
{
protected $signature = 'livewire:configure-s3-upload-cleanup';
protected $description = 'Configure temporary file upload s3 directory to automatically cleanup files older than 24hrs';
public function handle()
{
if (! FileUploadConfiguration::isUsingS3()) {
$this->error("Configuration ['livewire.temporary_file_upload.disk'] is not set to a disk with an S3 driver.");
return;
}
$driver = FileUploadConfiguration::storage()->getDriver();
// Flysystem V2+ doesn't allow direct access to adapter, so we need to invade instead.
$adapter = invade($driver)->adapter;
// Flysystem V2+ doesn't allow direct access to client, so we need to invade instead.
$client = invade($adapter)->client;
// Flysystem V2+ doesn't allow direct access to bucket, so we need to invade instead.
$bucket = invade($adapter)->bucket;
$prefix = FileUploadConfiguration::path();
$rules[] = [
'Filter' => [
'Prefix' => $prefix,
],
'Expiration' => [
'Days' => 1,
],
'Status' => 'Enabled',
];
$rules = $this->mergeRulesWithExistingConfiguration($client, $bucket, $prefix, $rules);
try {
$client->putBucketLifecycleConfiguration([
'Bucket' => $bucket,
'LifecycleConfiguration' => [
'Rules' => $rules
],
]);
} catch (\Exception $e) {
$this->error('Failed to configure S3 bucket ['.$bucket.'] to automatically cleanup files older than 24hrs!');
$this->error($e->getMessage());
return;
}
$this->info('Livewire temporary S3 upload directory ['.$prefix.'] set to automatically cleanup files older than 24hrs!');
}
private function checkIfLivewireConfigurationIsAlreadySet(array $existingConfigurationRules, string $bucket, S3Client $client, string $prefix) {
$existingConfigurationHasLivewire = collect($existingConfigurationRules)->contains('Filter.Prefix', $prefix);
if($existingConfigurationHasLivewire) {
$this->info('Livewire temporary S3 upload directory ['.$prefix.'] already set to automatically cleanup files older than 24hrs!');
$this->info('No changes made to S3 bucket ['.$bucket.'] configuration.');
exit;
}
}
private function mergeRulesWithExistingConfiguration(S3Client $client, string $bucket, string $prefix, array $rules): array
{
try {
$existingConfiguration = $client->getBucketLifecycleConfiguration([
'Bucket' => $bucket,
]);
} catch (\Exception $e) {
// if no configuration exists, we'll just ignore the error and continue.
$existingConfiguration = null;
}
if ($existingConfiguration) {
$this->checkIfLivewireConfigurationIsAlreadySet($existingConfiguration['Rules'], $bucket, $client, $prefix);
$existingConfiguration = $existingConfiguration['Rules'];
$rules = array_merge($existingConfiguration, $rules);
}
return $rules;
}
}
@@ -0,0 +1,61 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:stubs')]
class StubsCommand extends Command
{
protected $signature = 'livewire:stubs';
protected $description = 'Publish Livewire stubs';
protected $parser;
public function handle()
{
if (! is_dir($stubsPath = base_path('stubs'))) {
(new Filesystem)->makeDirectory($stubsPath);
}
file_put_contents(
$stubsPath.'/livewire.stub',
file_get_contents(__DIR__.'/livewire.stub')
);
file_put_contents(
$stubsPath.'/livewire.inline.stub',
file_get_contents(__DIR__.'/livewire.inline.stub')
);
file_put_contents(
$stubsPath.'/livewire.view.stub',
file_get_contents(__DIR__.'/livewire.view.stub')
);
file_put_contents(
$stubsPath.'/livewire.test.stub',
file_get_contents(__DIR__.'/livewire.test.stub')
);
file_put_contents(
$stubsPath.'/livewire.pest.stub',
file_get_contents(__DIR__.'/livewire.pest.stub')
);
file_put_contents(
$stubsPath.'/livewire.form.stub',
file_get_contents(__DIR__.'/livewire.form.stub')
);
file_put_contents(
$stubsPath.'/livewire.attribute.stub',
file_get_contents(__DIR__.'/livewire.attribute.stub')
);
$this->info('Stubs published successfully.');
}
}
@@ -0,0 +1,13 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
class TouchCommand extends MakeCommand
{
protected $signature = 'livewire:touch {name} {--force} {--inline} {--test} {--pest} {--stub=default}';
protected function configure()
{
$this->setHidden(true);
}
}
@@ -0,0 +1,32 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class AddLiveModifierToEntangleDirectives extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The @entangle(...) directive is now deferred by default.',
before: '@entangle(...)',
after: '@entangle(...).live',
pattern: '/@entangle\(((?:[^)(]|\((?:[^)(]|\((?:[^)(]|\([^)(]*\))*\))*\))*)\)(?!\.(?:defer|live))/',
replacement: '@entangle($1).live',
);
$this->interactiveReplacement(
console: $console,
title: 'The $wire.entangle function is now deferred by default and has been changed to $wire.$entangle.',
before: '$wire.entangle(...)',
after: '$wire.$entangle(..., true)',
pattern: '/\$wire\.entangle\(((?:[^)(]|\((?:[^)(]|\((?:[^)(]|\([^)(]*\))*\))*\))*)\)(?!\.(?:defer))/',
replacement: '$wire.$entangle($1, true)',
directories: ['resources/views', 'resources/js']
);
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class AddLiveModifierToWireModelDirectives extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The wire:model directive is now deferred by default.',
before: 'wire:model',
after: 'wire:model.live',
pattern: '/wire:model(?!\.(?:defer|lazy|live))/',
replacement: 'wire:model.live',
);
return $next($console);
}
}
@@ -0,0 +1,47 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeDefaultLayoutView extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
if($this->hasOldLayout())
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> The Livewire default layout has changed. </>');
$console->newLine();
$console->line('When rendering full-page components Livewire would use the "layouts.app" view as the default layout. This has been changed to "components.layouts.app".');
$choice = $console->choice('Would you like to migrate or keep the old layout?', [
'migrate',
'keep',
], 'migrate');
if($choice == 'keep') {
$console->line('Keeping the old default layout...');
$this->publishConfigIfMissing($console);
$console->line('Setting the default layout to "layouts.app"...');
$this->patternReplacement('/components\.layouts\.app/', 'layouts.app', 'config');
return $next($console);
}
$console->line('Setting the default layout to "components.layouts.app"...');
$this->patternReplacement('/layouts\.app/', 'components.layouts.app', 'config');
}
return $next($console);
}
protected function hasOldLayout()
{
return config('livewire.class_namespace') === 'layouts.app' || $this->filesystem()->exists('resources/views/layouts/app.blade.php');
}
}
@@ -0,0 +1,115 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\ComponentParser;
use Livewire\Features\SupportConsoleCommands\Commands\ComponentParserFromExistingComponent;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeDefaultNamespace extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
if($this->hasOldNamespace())
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> The Livewire namespace has changed. </>');
$console->newLine();
$console->line('The <options=underscore>App\\Http\\Livewire</> namespace was detected and is no longer the default in Livewire v3. Livewire v3 now uses the <options=underscore>App\\Livewire</> namespace.');
$choice = $console->choice('Would you like to migrate or keep the old namespace?', [
'migrate',
'keep',
], 'migrate');
if($choice === 'keep') {
$console->line('Keeping the old namespace...');
$this->publishConfigIfMissing($console);
$console->line('Setting the default namespace to "App\\Http\\Livewire"...');
$config = $this->filesystem()->get('config/livewire.php');
$config = str_replace('App\\\\Livewire', 'App\\\\Http\\\\Livewire', $config);
$this->filesystem()->put('config/livewire.php', $config);
return $next($console);
}
$componentNames = [];
$results = collect($this->filesystem()->allFiles('app/Http/Livewire'))
->filter(function($file) {
return str($file)->endsWith('.php');
})
->map(function($file) {
return str($file)->after('app/Http/Livewire/')->before('.php')->__toString();
})->map(function($component) use (&$componentNames) {
// Track component names to update namespace references later on.
$componentNames[] = $component;
$parser = new ComponentParser(
'App\\Http\\Livewire',
config('livewire.view_path'),
$component,
);
$newParser = new ComponentParserFromExistingComponent(
'App\\Livewire',
config('livewire.view_path'),
$component,
$parser
);
if ($this->filesystem()->exists($newParser->relativeClassPath())) {
return ['Skipped', $component, 'Already exists'];
}
if($this->filesystem()->directoryMissing(dirname($newParser->relativeClassPath()))) {
$this->filesystem()->createDirectory(dirname($newParser->relativeClassPath()));
}
$this->filesystem()->put($newParser->relativeClassPath(), $newParser->classContents());
$this->filesystem()->delete($parser->relativeClassPath());
return ['Migrated', $component];
});
foreach($componentNames as $name) {
$name = str($name)->replace('/', '\\\\')->toString();
// Update any namespace references
$this->patternReplacement(
pattern: "/App\\\Http\\\Livewire\\\({$name})/",
replacement: 'App\Livewire\\\$1',
directories: [
'app',
'resources/views',
'routes',
'tests',
]
);
}
// Update vite config
$this->patternReplacement(
pattern: '/App\/Http\/Livewire/',
replacement: 'App/Livewire',
mode: 'manual',
files: 'vite.config.js'
);
$console->table(
['Status', 'Component', 'Remark'], $results
);
}
return $next($console);
}
protected function hasOldNamespace()
{
return $this->filesystem()->exists('app/Http/Livewire') || config('livewire.class_namespace') === 'App\\Http\\Livewire';
}
}
@@ -0,0 +1,39 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeForgetComputedToUnset extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The forgetComputed component method is replaced by PHP\'s unset function',
before: '$this->forgetComputed(\'title\')',
after: 'unset($this->title)',
pattern: '/\$this->forgetComputed\((.*?)\);/',
replacement: function($matches) {
$replacement = '';
if(isset($matches[1])) {
preg_match_all('/(?:\'|")(.*?)(?:\'|")/', $matches[1], $keys);
$replacement .= 'unset(';
foreach($keys[1] ?? [] as $key) {
$replacement .= '$this->' . $key . ', ';
}
$replacement = rtrim($replacement, ', ');
$replacement .= ');';
}
// Leave unchanged if no replacement was possible.
return $replacement ?: $matches[0];
},
directories: 'app',
);
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeLazyToBlurModifierOnWireModelDirectives extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The wire:model.lazy modifier is now wire:model.blur.',
before: 'wire:model.lazy',
after: 'wire:model.blur',
pattern: '/wire:model.lazy/',
replacement: 'wire:model.blur',
);
return $next($console);
}
}
@@ -0,0 +1,53 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeTestAssertionMethods extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'assertEmitted is now assertDispatched.',
before: 'assertEmitted',
after: 'assertDispatched',
pattern: '/assertEmitted\((.*)\)/',
replacement: 'assertDispatched($1)',
directories: 'tests'
);
$this->interactiveReplacement(
console: $console,
title: 'assertEmittedTo is now assertDispatchedTo.',
before: 'assertEmittedTo',
after: 'assertDispatchedTo',
pattern: '/assertEmittedTo\((.*)\)/',
replacement: 'assertDispatchedTo($1)',
directories: 'tests'
);
$this->interactiveReplacement(
console: $console,
title: 'assertNotEmitted is now assertNotDispatched.',
before: 'assertNotEmitted',
after: 'assertNotDispatched',
pattern: '/assertNotEmitted\((.*)\)/',
replacement: 'assertNotDispatched($1)',
directories: 'tests'
);
$this->interactiveReplacement(
console: $console,
title: 'assertEmittedUp is no longer available.',
before: 'assertEmittedUp',
after: '<removed>',
pattern: '/->assertEmittedUp\(.*\)/',
replacement: '',
directories: 'tests'
);
return $next($console);
}
}
@@ -0,0 +1,23 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ChangeWireLoadDirectiveToWireInit extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The livewire:load is now livewire:init.',
before: 'livewire:load',
after: 'livewire:init',
pattern: '/livewire:load/',
replacement: 'livewire:init',
directories: ['resources/views', 'resources/js']
);
return $next($console);
}
}
@@ -0,0 +1,15 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class ClearViewCache extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$console->call('view:clear');
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class RemoveDeferModifierFromEntangleDirectives extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The @entangle(...) directive is now deferred by default.',
before: '@entangle(...).defer',
after: '@entangle(...)',
pattern: '/@entangle\(((?:[^)(]|\((?:[^)(]|\((?:[^)(]|\([^)(]*\))*\))*\))*)\)\.defer/',
replacement: '@entangle($1)',
);
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class RemoveDeferModifierFromWireModelDirectives extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The wire:model directive is now deferred by default.',
before: 'wire:model.defer',
after: 'wire:model',
pattern: '/wire:model\.defer/',
replacement: 'wire:model',
);
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class RemovePrefetchModifierFromWireClickDirective extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The wire:click.prefetch modifier has been removed.',
before: 'wire:click.prefetch',
after: 'wire:click',
pattern: '/wire:click\.prefetch/',
replacement: 'wire:click',
);
return $next($console);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class RemovePreventModifierFromWireSubmitDirective extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'The wire:submit directive now prevents submission by default.',
before: 'wire:submit.prevent',
after: 'wire:submit',
pattern: '/wire:submit\.prevent/',
replacement: 'wire:submit',
);
return $next($console);
}
}
@@ -0,0 +1,126 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class ReplaceEmitWithDispatch extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$console->newLine(2);
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> Partial Manual Upgrade: Event dispatching </>');
$console->newLine();
$console->line('In v2 you could use the emit() and dispatchBrowserEvent() methods in PHP.');
$console->line('For version 3, Livewire has unified these two methods into a single method: dispatch()');
$console->line('This step is partially automated, given parameters must now be named parameters, you will have to do this manually.');
$console->confirm('Ready to continue?');
$this->interactiveReplacement(
console: $console,
title: '$this->emit is now $this->dispatch.',
before: '$this->emit(\'post-created\');',
after: '$this->dispatch(\'post-created\')',
pattern: '/\$this->emit\((.*)\)/',
replacement: '$this->dispatch($1)',
directories: ['app', 'tests']
);
$this->manualUpgradeWarning(
console: $console,
warning: 'Please update the named parameters manually',
before: '$this->dispatch(\'post-created\', $post->id);',
after: '$this->dispatch(\'post-created\', postId: $post->id);'
);
$this->interactiveReplacement(
console: $console,
title: '$this->emitTo() is now $this->dispatch()->to().',
before: '$this->emitTo(\'foo\', \'post-created\');',
after: '$this->dispatch(\'post-created\')->to(\'foo\')',
pattern: '/\$this->emitTo\((["\'][a-z0-9-.]*["\']),\s?([|"\'][a-zA-Z0-9-.]*["\'])(?:[,])?\s?(.*)\);/',
replacement: function($matches) {
$component = $matches[1];
$eventName = $matches[2];
$eventData = $matches[3];
if (empty($eventData)) {
return "\$this->dispatch({$eventName})->to($component);";
}
return "\$this->dispatch({$eventName}, $eventData)->to($component);";
},
directories: ['app', 'tests']
);
$this->manualUpgradeWarning(
console: $console,
warning: 'Please update the named parameters manually',
before: '$this->dispatch(\'post-created\', $post->id)->to(\'foo\');',
after: '$this->dispatch(\'post-created\', postId: $post->id)->to(\'foo\');'
);
$this->interactiveReplacement(
console: $console,
title: '$this->emitSelf() is now $this->dispatch()->self().',
before: '$this->emitSelf(\'post-created\');',
after: '$this->dispatch(\'post-created\')->self();',
pattern: '/\$this->emitSelf\((.*)\)/',
replacement: '\$this->dispatch($1)->self()',
directories: ['app', 'tests']
);
$this->manualUpgradeWarning(
console: $console,
warning: 'Please update the named parameters manually',
before: '$this->dispatch(\'post-created\', $post->id)->self();',
after: '$this->dispatch(\'post-created\', postId: $post->id)->self();'
);
$this->interactiveReplacement(
console: $console,
title: '$this->dispatchBrowserEvent() is now $this->dispatch().',
before: '$this->dispatchBrowserEvent(\'post-created\');',
after: '$this->dispatch(\'post-created\');',
pattern: '/\$this->dispatchBrowserEvent\((.*)\)/',
replacement: '\$this->dispatch($1)',
directories: ['app', 'tests']
);
$this->manualUpgradeWarning(
console: $console,
warning: 'Please update the named parameters manually',
before: '$this->dispatch(\'post-created\', [\'postId\' => $post->id]);',
after: '$this->dispatch(\'post-created\', postId: $post->id);'
);
$this->interactiveReplacement(
console: $console,
title: 'The $emit helper is now $dispatch.',
before: '$emit(\'post-created\');',
after: '$dispatch(\'post-created\')',
pattern: '/\$emit\((.*)\)/',
replacement: '\$dispatch($1)',
directories: ['resources']
);
$this->manualUpgradeWarning(
console: $console,
warning: 'Please update the named parameters manually',
before: '$dispatch(\'post-created\', 1);',
after: '$dispatch(\'post-created\', {postId: 1});'
);
$this->manualUpgradeWarning(
console: $console,
warning: 'The concept of `emitUp` has been removed entirely. Events are now dispatched as actual browser events and therefore "bubble up" by default.',
before: ['$this->emitUp(\'post-created\');', '$emitUp(\'post-created\', 1)'],
after: ['<removed>', '<removed>'],
);
if($console->confirm('Continue?', true))
{
return $next($console);
}
}
}
@@ -0,0 +1,24 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class ReplaceTemporaryUploadedFileNamespace extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$this->interactiveReplacement(
console: $console,
title: 'Livewire\TemporaryUploadedFile is now Livewire\Features\SupportFileUploads\TemporaryUploadedFile.',
before: 'Livewire\TemporaryUploadedFile',
after: 'Livewire\Features\SupportFileUploads\TemporaryUploadedFile',
pattern: '/Livewire\\\\TemporaryUploadedFile/',
replacement: "Livewire\\Features\\SupportFileUploads\\TemporaryUploadedFile",
directories: ['app', 'tests', 'resources/views']
);
if ($console->confirm('Continue?', true)) {
return $next($console);
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Livewire\Features\SupportConsoleCommands\Commands\UpgradeCommand;
class RepublishNavigation extends UpgradeStep
{
public function handle(UpgradeCommand $console, \Closure $next)
{
if($this->filesystem()->directoryExists('resources/views/vendor/livewire')) {
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> The Livewire pagination views have changed. </>');
$console->newLine();
$console->line('Republishing of the pagination views is required.');
$confirm = $console->confirm('Do you want to republish the pagination views?', true);
if($confirm) {
$console->call('vendor:publish', [
'--tag' => 'livewire:pagination',
'--force' => true,
]);
}
}
return $next($console);
}
}
@@ -0,0 +1,23 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class ThirdPartyUpgradeNotice extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> Third-party package upgrade 🚀 </>');
$console->newLine();
$console->comment('!! Please be aware that the following upgrade steps are registered by third-parties !!');
$console->newLine();
$console->newLine();
$console->line('You can abort this command at any time by pressing ctrl+c.');
if($console->confirm('Ready to continue?', true))
{
return $next($console);
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class UpgradeAlpineInstructions extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> Manual Upgrade: Remove Alpine references </>');
$console->newLine();
$console->line('Livewire version 3 ships with AlpineJS by default.');
$console->line('If you use Alpine in your Livewire application, you will need to remove it and any of the plugins listed below so that Livewire\'s built-in version doesn\'t conflict with it.');
$console->line('Livewire version 3 now also ships with the following Alpine plugins:');
$console->line('- Intersect');
$console->line('- Collapse');
$console->line('- Persist');
$console->line('- Morph');
$console->line('- Focus');
$console->line('- Mask');
$console->newLine();
$console->line('If you were accessing Alpine via JS bundle you can now import Livewire\'s ESM module instead and call Livewire.start() when ready, for example:');
$console->newLine();
$console->line('import { Livewire, Alpine } from \'../../vendor/livewire/livewire/dist/livewire.esm\';');
$console->line('Alpine.plugin(yourCustomPlugin);');
$console->line('Livewire.start();');
if($console->confirm('Continue?', true))
{
return $next($console);
}
}
}
@@ -0,0 +1,24 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class UpgradeConfigInstructions extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> Manual Upgrade: New configuration </>');
$console->newLine();
$console->line('Livewire V3 has both added and removed certain configuration items.');
$console->line('If your application has a published configuration file `config/livewire.php`, you will need to update it to account for the following changes.');
$console->line('Added options: legacy_model_binding, inject_assets, inject_morph_markers, and navigate');
$console->line('Removed options: app_url, middleware_group, manifest_path, back_button_cache');
$console->newLine();
if($console->confirm('Continue?', true))
{
return $next($console);
}
}
}
@@ -0,0 +1,27 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Console\Command;
class UpgradeIntroduction extends UpgradeStep
{
public function handle(Command $console, \Closure $next)
{
$console->line('<fg=#FB70A9;bg=black;options=bold,reverse> LIVEWIRE v2 to v3 UPGRADE 🚀 </>');
$console->newLine();
$console->comment('!! Running this command multiple times may result in incorrect replacements !!');
$console->newLine();
$console->line('This command will help you upgrade from Livewire v2 to v3.');
$console->newLine();
$console->line('<options=underscore>Files will be modified in-place, so make sure you have a backup of your project before continuing.</>');
$console->newLine();
$console->newLine();
$console->line('You can abort this command at any time by pressing ctrl+c.');
if($console->confirm('Ready to continue?', true))
{
return $next($console);
}
}
}
@@ -0,0 +1,142 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands\Upgrade;
use Illuminate\Support\Arr;
use Closure;
use Illuminate\Console\Command;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
abstract class UpgradeStep
{
public function filesystem(): FilesystemAdapter
{
return Storage::build([
'driver' => 'local',
'root' => base_path(),
]);
}
public function publishConfigIfMissing($console): bool
{
if($this->filesystem()->missing('config/livewire.php')) {
$console->line('Publishing Livewire config file...');
$console->newLine();
$console->call('vendor:publish', [
'--tag' => 'livewire:config',
]);
return true;
}
return false;
}
public function manualUpgradeWarning($console, $warning, $before, $after)
{
$console->newLine();
$console->error($warning);
$console->newLine();
$this->beforeAfterView(
console: $console,
before: $before,
after: $after,
);
$console->confirm('Ready to continue?');
}
public function beforeAfterView($console, $before, $after, $title = 'Before/After example')
{
$console->table(
[$title],
[
array_map(fn($line) => "<fg=red>- {$line}</>", Arr::wrap($before)),
array_map(fn($line) => "<fg=green>+ {$line}</>", Arr::wrap($after))
],
);
}
public function interactiveReplacement(Command $console, $title, $before, $after, $pattern, $replacement, $directories = ['resources/views'])
{
$console->newLine(4);
$console->line("<fg=#FB70A9;bg=black;options=bold,reverse> {$title} </>");
$console->newLine();
$console->line('Please review the example below and confirm if you would like to apply this change.');
$console->newLine();
$this->beforeAfterView($console, $before, $after);
$confirm = $console->confirm('Would you like to apply these changes?', true);
if ($confirm) {
$console->newLine();
$replacements = $this->patternReplacement($pattern, $replacement, $directories);
if($replacements->isEmpty())
{
$console->line('No occurrences of were found.');
}
if($replacements->isNotEmpty()) {
$console->table(['File', 'Occurrences'], $replacements);
}
}
$console->newLine(4);
}
public function patternReplacement(
$pattern,
$replacement,
$directories = 'resources/views',
$files = [],
$mode = 'auto')
{
// If the mode is auto, we'll just get all the files in the directories
if($mode == 'auto') {
$files = collect(Arr::wrap($directories))->map(function($directory) {
return collect($this->filesystem()->allFiles($directory))->map(function ($path) {
return [
'path' => $path,
'content' => $this->filesystem()->get($path),
];
});
})->flatten(1);
}
// If the mode is manual, we'll just use the files passed in
if($mode == 'manual') {
$files = collect(Arr::wrap($files))->map(function($path) {
return [
'path' => $path,
'content' => $this->filesystem()->get($path),
];
});
}
return $files->map(function($file) use ($pattern, $replacement) {
if($replacement instanceof Closure) {
$file['content'] = preg_replace_callback($pattern, $replacement, $file['content'], -1, $count);
} else {
$file['content'] = preg_replace($pattern, $replacement, $file['content'], -1, $count);
}
$file['occurrences'] = $count;
return $count > 0 ? $file : null;
})
->filter()
->values()
->map(function($file) {
$this->filesystem()->put($file['path'], $file['content']);
return [
$file['path'], $file['occurrences'],
];
});
}
}
@@ -0,0 +1,84 @@
<?php
namespace Livewire\Features\SupportConsoleCommands\Commands;
use Illuminate\Console\Command;
use Illuminate\Pipeline\Pipeline;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\AddLiveModifierToEntangleDirectives;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\AddLiveModifierToWireModelDirectives;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeDefaultLayoutView;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeDefaultNamespace;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeLazyToBlurModifierOnWireModelDirectives;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeTestAssertionMethods;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeWireLoadDirectiveToWireInit;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ClearViewCache;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\RemoveDeferModifierFromEntangleDirectives;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\RemoveDeferModifierFromWireModelDirectives;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\RemovePrefetchModifierFromWireClickDirective;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\RemovePreventModifierFromWireSubmitDirective;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\RepublishNavigation;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ThirdPartyUpgradeNotice;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\UpgradeAlpineInstructions;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\UpgradeConfigInstructions;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ReplaceEmitWithDispatch;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ReplaceTemporaryUploadedFileNamespace;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\UpgradeIntroduction;
use Livewire\Features\SupportConsoleCommands\Commands\Upgrade\ChangeForgetComputedToUnset;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'livewire:upgrade')]
class UpgradeCommand extends Command
{
protected $signature = 'livewire:upgrade {--run-only=}';
protected $description = 'Interactive upgrade helper to migrate from v2 to v3';
protected static $thirdPartyUpgradeSteps = [];
public function handle()
{
app(Pipeline::class)->send($this)->through(collect([
UpgradeIntroduction::class,
// Automated steps
ChangeDefaultNamespace::class,
ChangeDefaultLayoutView::class,
AddLiveModifierToWireModelDirectives::class,
RemoveDeferModifierFromWireModelDirectives::class,
ChangeLazyToBlurModifierOnWireModelDirectives::class,
AddLiveModifierToEntangleDirectives::class,
RemoveDeferModifierFromEntangleDirectives::class,
RemovePreventModifierFromWireSubmitDirective::class,
RemovePrefetchModifierFromWireClickDirective::class,
ChangeWireLoadDirectiveToWireInit::class,
RepublishNavigation::class,
ChangeTestAssertionMethods::class,
ChangeForgetComputedToUnset::class,
ReplaceTemporaryUploadedFileNamespace::class,
// Partially automated steps
ReplaceEmitWithDispatch::class,
// Manual steps
UpgradeConfigInstructions::class,
UpgradeAlpineInstructions::class,
// Third-party steps
... static::$thirdPartyUpgradeSteps,
ClearViewCache::class,
])->when($this->option('run-only'), function($collection) {
return $collection->filter(fn($step) => str($step)->afterLast('\\')->kebab()->is($this->option('run-only')));
})->toArray())
->thenReturn();
}
public static function addThirdPartyUpgradeStep($step)
{
if(empty(static::$thirdPartyUpgradeSteps)) {
static::$thirdPartyUpgradeSteps[] = ThirdPartyUpgradeNotice::class;
}
static::$thirdPartyUpgradeSteps[] = $step;
}
}
@@ -0,0 +1,11 @@
<?php
namespace DummyNamespace;
use Livewire\Attribute as LivewireAttribute;
#[\Attribute]
class DummyClass extends LivewireAttribute
{
//
}
@@ -0,0 +1,11 @@
<?php
namespace DummyNamespace;
use Livewire\Attributes\Validate;
use Livewire\Form;
class DummyClass extends Form
{
//
}

Some files were not shown because too many files have changed in this diff Show More