-
Notifications
You must be signed in to change notification settings - Fork 0
Registering and resolving components
Think about the ways in which you might reference a Livewire component...
A) When generating it
artisan make:livewire some-component
B) When rendering it in a blade view
<livewire:some-component />
<!-- Or: -->
@livewire('some-component')C) When using it as a full-page component for a route
Route::get('/', SomeComponent::class);D) When testing it
Livewire::test('some-component')
...E) When registering an alias for it
Livewire::component('some-component', YourComponent::class);F) When emitting an event to it
$this->emit('some-event')->to('some-component');G) When Livewire internally needs to identify a component within a snapshot
As you can see above, there are two ways to identify a component:
- By its class name (
App\Http\Livewire\SomeComponent::class) - By its name (
some-component)
In a perfect world, we would always refer to a component by its class name in every instance and there would be no problem.
However, there are scenarios where it is either preferred or necessary to refer to a component by its name.
Here are those scenarios:
- [Preferred] IMO it's easier to use the simple name when generating a component via command line
artisan make:livewire some-component - [Necessary] To not expose information about a users system to the browser, we only send the component name to the browser in the snapshot
- [Necessary] For Livewire component tags in Blade (
<livewire:...>) it is unable to parse uppercase and backslashes in class names (and it just doesn't look conventional)
This shouldn't be a problem because Livewire uses a convention to convert back and forth between a component's class name representation and its simple name.
This convention is as follows:
Given a Livewire component class: App\Http\Livewire\Posts\CreatePost::class
graph TD;
A[App\Http\Livewire\Posts\CreatePost] --> |remove the configured `livewire.class_namespace`, App\Http\Livewire by default| B[Posts\CreatePost]
B --> |convert backslashes to dots| C[Posts.CreatePost]
C --> |convert to kebab-notation| D[posts.create-post]
Notice that this process can be reversed to generate the class of a component from its name.
In cases where you have a Livewire in a completely different part of your app, you need to manually register it like so:
Livewire::component('some-component', App\DifferentPart\SomeComponent::class);
Now Livewire cannot rely on auto-generating a name for this component.
Instead, it must look in a registry to see if an alias was registered and use THAT for the name instead.
This is fine, we can still convert a class to a name, and the reverse.
The problem arises when a user registers a component without giving it a name:
Livewire::component(App\DifferentPart\SomeComponent::class);
Livewire supports this syntax because it's nicer to use than having to manually name every non-auto-discovered component.
The problem that arises now, is that we can no longer freely convert back and forth between name and class.
To understand the problem better, let me lead you to it by asking you a few questions:
Given: Livewire::component(App\DifferentPart\SomeComponent::class);
We need to generate a name for this component to send to the browser. How would we generate a name for this? And again let me remind you that we don't want to reveal the actual class namespace to the browser.
Let's explore a few options:
A) Hashing it
We COULD hash App\DifferentPart\SomeComponent::class using something like md5, assuming it can't be easily reverse-engineered (which sometimes it can using rainbow tables).
So let's say running md5('App\DifferentPart\SomeComponent::class') we get: a74edf126996771f63670897c050a4f4
Can we use THAT for the name?
No, because how would we convert it back into the class name? It's a hash, so it's only one way.
EDIT: Well, because we KNOW all the registered components, we can run through that list, re-hash them, compare and find a match. This is actually a decent solution to the problem and is probably the way we should go lololol. I had to write all this down to think that through lol. If I had a nickel...
B) Encrypt it Because we couldn't reverse the hash and turn a name back into a class, what if we two-way encrypt it using something like:
Hash::make('App\DifferentPart\SomeComponent') to get: $2y$10$M3BpPO9DZVmlU/IYDbdJherlBxIDfQU0hUOEZUa5iWoaIvnNSi1ZW
NOW we can reverse it back into the class name.
Yes, that's true, but there are two problems with this strategy:
A) Encrypting in a secure way (like this is, using the App's key) is slow B) The size of the string is always larger (sometimes multiples) than the original name. Not a huge deal, but those bytes add up in payload sizes. C) We are giving an attacker more surface area to reverse engineer the encryption, extract the App's key and now have made the entire Laravel app susceptible to session manipulation attacks (and probably more)
C) Generate a key and put it in a persistent lookup
Ok, rather than encrypting, we COULD generate a random string for the name and create some kind of key-value lookup in the backend. Maybe it's a manifest file that looks like:
[
'ah3' => 'App\DifferentPart\SomeComponent',
]
Now we can use the string 'ah3' as the component name and use this lookup file to convert back and forth between the component name and class name.
THIS is a problem because if an app uses a load-balancer, we would need this lookup to be shared across instances and because we generated this key randomly in a non-deterministic way, the only way to share it would be to physically share it, which would cost us on the performance side (using a shared cache, database, or worse...)