Blog
Dealing with ReferenceError can't access lexical declaration before initialization issues in an Angular application
Posted on: 2022-10-20 17:11:59So, there you are, minding your own business when you start getting an error that looks like this:
ERROR Error: Uncaught (in promise): ReferenceError: can't access lexical declaration 'NewState' before initialization NewState@http://localhost:8100/default-src_app_modules_messaging_messaging-components_messaging-components_module_ts-src_app_pages-f15b60.js:248:64
46129@http://localhost:8100/default-src_app_modules_messaging_messaging-components_messaging-components_module_ts-src_app_pages-f15b60.js:613:8
__webpack_require__@http://localhost:8100/runtime.js:23:42
3982@http://localhost:8100/default-src_app_modules_messaging_messaging-components_messaging-components_module_ts-src_app_pages-f15b60.js:258:99
__webpack_require__@http://localhost:8100/runtime.js:23:42
9851@http://localhost:8100/default-src_app_modules_messaging_messaging-components_messaging-components_module_ts-src_app_pages-f15b60.js:218:102
__webpack_require__@http://localhost:8100/runtime.js:23:42
78016@http://localhost:8100/src_app_modules_chat_pages_chat-messages_chat-messages_module_ts.js:22:138
__webpack_require__@http://localhost:8100/runtime.js:23:42
If you search for this error, you'll find lots of links to the MDN article on ReferenceError
but this isn't helpful. Beacuse at this point you've got TypeScript + Webpack mangling your output so badly that you have absolutely no idea what's going on.
Well, here's the thing, Angular & Webpack do a pretty good job of making sure that you're not hoisting things out of order or doing weird things with your dependency. Here's your real problem:
You've got a circular dependency
But wait, doesn't Angular already check for circular dependencies? Yeah. Obvious ones, they sure do. You can't have an NgModule
that references another NgModule
which references back to the other NgModule
. It would detect that and throw an error.
Here's an example: https://stackblitz.com/edit/angular-ivy-ez3evu?file=one.module.ts,two.module.ts
It doesn't even compile. It just stack overflows.
I tried to reproduce the actual issue we had in StackBlitz... it didn't work.
The truth is, some circular references are just not detected without a bit of extra work.
Angular CLI to the rescue: ng build --show-circular-dependencies
Warning: Circular dependency detected:
src/app/modules/chat/state/conversations/small-conversation.state.ts ->
src/app/pages/trainer-messages/trainer-messages.page.ts ->
src/app/modules/chat/state/conversations/small-conversation.state.ts
This revealed our state model was dependent upon a page... that depends on our state model...? What?
It was an enum
Ultimately, we had an enum in that page's file and that file depended upon the state model itself. Moving that enum to a new file and including it on both pages fixed the issue.
Here's hoping this page helps other people!
Continue reading...Debugging PHP applications on Ubuntu
Posted on: 2022-04-19 15:57:13I'm cleaning out some notes and wanted to put this somewhere...
On Ubuntu
- Crashes go to /var/crash.
- Make sure ulimit -c unlimited is run. Restart php-fpm.
Or see: https://bugs.php.net/bugs-generating-backtrace.php
In order to “unpack them” you use apport-unpack <crash> ~/destination
.
See: https://askubuntu.com/questions/434431/how-can-i-read-a-crash-file-from-var-crash
Then, you can use gdb
cat ExecutablePath CoreDump
within that directory to examine it.
You’ll need the debug symbols loaded for php-fpm. If you’re using the one from sury: https://github.com/oerdnj/deb.sury.org/issues/512
Basically, add the main/debug repos and then apt-get install the correct php7.4-fpm-dbgsym (or 8.0, whatever, you do you)
You also probably want the dbg helpers for zend: https://reverseengineering.stackexchange.com/questions/9558/how-to-get-current-php-function-name-in-gdb https://derickrethans.nl/what-is-php-doing.html
Get the current version of .gdbinit: https://github.com/php/php-src/blob/master/.gdbinit
Then you can review the stacktraces...
Continue reading...Strong like steel? What about the mighty oak?
Posted on: 2022-02-03 10:26:32It's sleeting outside right now and while many people are having flashbacks to the event of last year. But one of the things that came to mind as I looked outside this morning was the trees in the front yard.
When people think about strength, we often think of steel. As in the "Man of Steel". And while steel has been used to create some of the biggest structures in existence, steel isn't perfect for everything.
While steel is strong, steel can form hidden cracks. When it finally does break, it can be catastrophic.
But oaks. Oaks may not be as strong as steel directly. But they are magnificent and mighty.
When it snowed and iced last year, our pin-oak's branches drooped nearly 9 feet until they basically touched the ground. Walking around outside after the snow, you could hear branches of the Juniper (Texas Cedar) trees snapping in the cold and the weight of of the snow.
I can't pull down a branch on my own, but the snow can weigh it down. But after the snow melts, the branches are restored and the oak is no worse for wear.
All that said, when we think about strength, we should consider the oak, which can bend and twist, be stripped naked of its leaves, be taken down nearly to the ground, but yet can withstand it all.
Continue reading...Hot code updates for Cordova applications
Posted on: 2022-01-23 21:18:27A long time ago, I used and (mostly) loved Ionic's Live Update (iirc, I think it was called Deploy in the past) functionailty. The idea is really simple. Your "app" lives on-device in a www/
directory that gets served up by Cordova. There really isn't anything magical about it but why can't you just check to see if your application has updates files every time you start it up? And, if it does, update your files and then launch the app?
Well, that's what Live Update does.
There are some caveats, though.
Obviously, the updates can't add/remove/update Swift/Objective-C libraries or modules. Bugs or improvements in those modules can't managed through this method.
The only real "gotcha" here is that Ionic makes you pay for Deploy credits. By default you currently get 25k per month. This is probably enough if you want to update a small group of users a few times a day, but it isn't enough if you want to push out a large number of updates across the board.
In a way, this whole thing sort-of offends my sensibilities. Why would I pay per-device to deliver a www/
directory, when CDNs and other systems allow me to do this for pennies.
Well, if you're offended by this, like me, then you probably are wondering if there are other options.
Well, sort of.
Here's what I've found in my research.
- https://github.com/mnill/cordova-app-updater - Seems like it allows you to make individual file updates available to your Cordova application. Probably not helpful for larger applications or ones that use generated code modules from webpack. Hasn't been updated in over 4 years. Dead-end.
- https://github.com/nordnet/cordova-hot-code-push - Also deprecated. Updated about 3 years ago as of time of writing. There appears to be a handful of offshoots that have some recent updates. (This one in particular has been updated in the last few weeks.)
-
https://github.com/Microsoft/cordova-plugin-code-push - Marked as read-only, this system was effectively a competitor to Appflow Deploy (or at least, it seemed to be). So unless you're planning on using
CodePush (now App Center)(nevermind, they killed off Cordova support completely). Looks like some folks are talking about writing a replacement for CodePush but it doesn't seem that much progress has been made. Dead-end.
Looks like there are 2 main options here:
- Use the
chcp
(cordova-hot-code-push
) module. - Set up Appflow Deploy.
Eloquent upsert() requires a UNIQUE index on MySQL
Posted on: 2021-10-29 16:19:48tl;dr - upsert()
acts different based on your DB. MySQL requires that a UNIQUE INDEX
exist for the combination of column you are wanting upsert to work on.
Where I can, I've been using upsert() as a way to make many queries into (usually) a single query.
Recently, I had a situation where a function in our code base was essentially doing this:
$model->relation->delete();
foreach ($anArray as $newModels) {
$model->relation()->create($anArray);
}
This operation was wrapped in a DB::beginTransaction()
/ DB::commit()
.
When I finally got around to re-writing it, it was much cleaner and of course looked something like this:
RelatedModel::upsert(
$models,
['key_one', 'key_two', 'key_three'].
['col1', 'col2']
);
I ran the test suite (it passed), pushed and went to sleep.
When I woke up the next morning, I saw a number of errors and a large number of records being generated. Obviously, something was not right in the world of upsert
.
After digging into things and exploring how MySQL "compiles" upserts, it turns out that this:
RelatedModel::upsert(
$models,
['key_one', 'key_two', 'key_three'].
['col1', 'col2']
);
Also could be written like this:
RelatedModel::upsert(
$models,
[].
['col1', 'col2']
);
That second set of parameters is never used in the function:
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$sql = $this->compileInsert($query, $values).' on duplicate key update ';
$columns = collect($update)->map(function ($value, $key) {
return is_numeric($key)
? $this->wrap($value).' = values('.$this->wrap($value).')'
: $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
return $sql.$columns;
}
$uniqueBy
isn't used. It is in the SQLite version, but not here.
After cleaning up the DB and adding an index, the upsert
will work as expected.
Notes on writing reproducable Laravel tests
Posted on: 2021-09-22 11:46:22Just writing some notes down on things that have made my life easier:
Testing timestamps
This has kicked my butt so many times. Started using Carbon::setTestNow(now())
and Carbon::now()
to test timestamps coming back from API calls. You may ask: why do you care? This is beacuse I want to know the format of what comes back. I suppose I could just write a custom assertion that lets me know if the timestamp is within, say, the last second or something like that and matches a valid format. This might work. But I like being precise.
Fully test the "shape" of responses
One of the big things that I've come to rely on tests for is to make sure that I don't accidentally break stuff when refactoring or making changes.
All of our new code is testing such that the entire structure of the response is tested to make sure it's good. We also then test some of the data points to make sure things are where they should be.
$this->actingAs($user, 'api')
->getJson(route('whatever'))
->assertJsonStructure([
'data' => [
'id',
'created_at',
'updated_at',
...
],
])
->assertJsonPath('data.id', $model->id)
A couple of times I refactored something and ended up accidentally forgetting (or renaming a value) because a test didn't cover it. APIs are contracts. They should always return the expected data.
Testing the DB, too.
I've also gotten into a habit of testing the DB after certain API calls, too. This is helpful for testing both what should be there and also for what shouldn't be there. Yes, the tests are longer. This isn't as necessary as much anymore as we've started pulling stuff out of controllers, but on older code this can be valuable, too.
Testing with no internet connection
Turns out we've accidentally had tests that reached out to production systems to pull data. Testing while offline finds these tests and allows you refactor/abstract them.
Knowing when to use fake/random values
This is actually a big one for me. For several weeks I fought with tests that would randomly fail.
Turns out the problem was that the models being tested had a flag on their factory that was choosing a random value. This random value determined if the user had an active subscription.
Now, we assume free and require paid using a factory state: User::factory()->paid()->create()
. This ensures that the tests are clear and we've stopped running into this problem. However, here are some fields you likely shouldn't be using random data for:
- status (paid_status, post status)
- state (sending, processing, etc.)
- type (public, private, shared posts, etc.)
- flags (reported, archived, hidden)
- tags (any tags, really)
Modifying Eloquent's relations upon loading
Posted on: 2021-09-01 14:06:48In our application's backend, we have a model that is very large to handle our Training programs. Part of the reason it's so large is because there are a lot of layers to give flexibility to training programmers. For a normal user who is doing a workout, their session looks like this:
,-------.
|Session|
|-------|
`-------'
|
,-------.
|Workout|
|-------|
`-------'
/ \
,----------------. ,------.
|Training Program| |Groups|
|----------------| |------|
`----------------' `------'
|
,-----------------.
|MovementInstances|
|-----------------|
`-----------------'
|
,--------.
|Movement|
|--------|
`--------'
One of the things we really have been wanting to implement is the ability to "swap" a Movement
for another. If you've ever done a work out, especially an advanced one, a good trainer will normally start with a base workout and then will scale it to an individual. If you've ever done Crossfit, you've done this.
In the end, this is essentially what we are wanting to accomplish:
$swap = [
'original_movement' => 'push-up',
'new_movement' => 'knee push-up'
];
// true, this is the old one.
$workout->groups[0]->movements[0]->movement === 'push-up';
$workout->groups[0]->originalMovements[0]->movement === 'push-up';
$workout->setSwaps([$swap]);
// now is the new one but original is unchanged.
$workout->groups[0]->movements[0]->movement === 'knee push-up';
$workout->groups[0]->originalMovements[0]->movement === 'push-up';
We have a lot of code that leverages Eloquent's relations to load and build responses for our API. However, the API isn't the only thing that will need this information. Ultimately, we cannot simply depend on the API/resources to format a response for a user, we need to be able to ensure that for a given point in time, a user's workout representation can be easily generated.
So updating our resource models to somehow transform the data is out.
When thinking about it from a DX perspective first, this is how I originally want to be able to do things:
- When loading a user's training
Session
, I want their swaps to automatically apply. - When loading a
Workout
(outside of a session), swaps should be able to be selectively applied (e.g.$workout->setSwaps(...)
). - A minimum amount of changes should be done to the rest of the API except for adding the necessary attributes to the respective resources.
A trait and an override gets us most of the way
What I ended up doing was creating a trait AppliesMovementSwaps
that exposes 3 different implementation points:
- Setting
$inheritSwapsRelation
denotes which relation may contain swap information. This allowssetSwaps()
on a model to talk to a relation to grab their swap information. - Setting
$appliesSwapsRelation
denotes a "child" relation which may want to take action when adding swaps from "above". - Classes can implement
applySwaps()
to take action when swaps are added. More on this in a moment.
So now the classes look like this (they all use AppliesMovementSwaps
).
,---------------------------------.
|Session |
|---------------------------------|
|$appliesSwapsRelation = 'workout'|
`---------------------------------'
|
,----------------------------------.
|Workout |
|----------------------------------|
|$appliesSwapsRelation = 'groups' |
|$inheritsSwapsRelation = 'session'|
`----------------------------------'
|
,----------------------------------.
|Groups |
|----------------------------------|
|$inheritsSwapsRelation = 'workout'|
`----------------------------------'
Additionally:
- The
Groups
model (where the swaps are applied) had the relation that needed to be modified (exercises
) duplicated so that the original is always available (e.g.originalExercises
). - A pointer to the
originalExercises
relation was created.
This was done so that one relation always points to what was programmed for the workout. exercises
is what the user sees and that's the the relation that gets modified.
Finally, (and most importantly) I overrode setRelation
on the Group
model so that I could apply the transformation and "swap" out the records. This allows the relation to be modified and continue to work as a regular relation even though we are doing things with it.
public function setRelation($relation, $value)
{
if ($relation === 'movements' && count($this->getSwaps()) > 0) {
$value = $this->movementsWithSwaps($value);
}
return parent::setRelation($relation, $value);
}
This all should work in theory. In practice, relations make things a little bit harder.
Modifying relations with setRelation
The above mostly works. But it doesn't in some instances. Here is an example:
// Load workout, apply swaps.
$workout->setSwaps([...]);
$workout->load('groups.movements');
$workout->groups[0]->movements[0]->movement === 'knee push-ups';
// This doesn't.
$workout->setSwaps([...]);
$workout->groups[0]->movements[0]->movement === 'push-ups';
// Wait, this works?
$workout->groups[0]->movements[0]->movement === 'knee push-ups';
... what is going on?
Eloquent + setRelation
When Eloquent is accessing a relation:
$workout->groups[0]->movements[0]->movement
^^^^^^^^^ This is a relation.
^^^^^^ This is too, but not relevant.
It ends up calling a small shrub of functions:
Model->__get()
HasAttributes->getAttribute()
HasAttributes->getRelationValue()
HasAttributes->getRelationshipFromMethod()
It is here within getRelationshipFromMethod()
where the issue is manifested:
protected function getRelationshipFromMethod($method)
{
$relation = $this->$method();
// <snip>...
return tap($relation->getResults(), function ($results) use ($method) {
$this->setRelation($method, $results);
});
}
Huh. So the first time you load a relation, it gets tap()
'd and during the tap setRelation
gets called. This means that the first access doesn't see the modified values that we do in setRelation
only on second access does it pull from the relation value stored on the model. Shucks.
A hack to make it work
To make it work, we ended up hacking applySwaps()
to force the reload of those certain relations when swaps were being applied. This means the relation value is primed for any access.
So far, this works really well. It feels hackish, but all of the tests (nearly 2 dozen) are passing so... yay?
Alternatives?
Here's things that could make this easier:
- Add the ability to
transform()
a relation before it gets loaded. This would remove the need to overridesetRelation
. This seems clean and could be easy to do. - Add support for a
get{Relation}Relation()
method? I looked at Laravel Relationship Events but it didn't support the transformation. - Rewrite the models to simply support a getter. This is probably the "right" way but would likely have required a lot more time since right now everything is handled using relations.
Databases inspection while debugging tests
Posted on: 2021-05-04 13:21:39While debugging, I use a local MySQL database. Sometimes, I want to inspect the contents of the DB while the tests are running to I can check out the state of things.
This is how I do it.
Add a breakpoint to your code, and then open up your query environment of choice:
set tx_isolation="READ-UNCOMMITTED";
Now run whatever queries you want to inspect the DB.
The tx_isolation
change will only impact the current session.
Don't put services in a Laravel Console command class constructor
Posted on: 2021-04-20 22:33:15It will attempt to resolve the component, and that might not take place when you want it to.
You know, for instance, before your cached config gets cleared and composer dumpautoload
tries to run and barfs.
404 when deleting a model through Livewire
Posted on: 2021-04-06 15:35:43LiveWire has become a new favorite thing of mine the last few weeks. It scratches the itch that I've had for a long time now: I want some flexibility in the UI to do some very simple AJAX and I want to stay in Blade templates.
Anyway, I had an interesting issue which looked like this:
- Open up a page with a Livewire form on it.
- Submit the form, causing a new model to be created.
- This model now has a delete button: so click on it.
- Livewire shows a 404.
After looking at the HTML coming through the wire, I noticed that the relations Livewire was sending, there was something not right.
Livewire sends some meta-data back to the browser related to your component. It does this, probably, so that it can just rehydrate the models on the component without the original context of the request which was originally used to generate the component.
At any rate, the model's ID that I was generating was 0
. Something about the model wasn't serializing properly.
Turns out that I hadn't actually set $incrementing = false
nor $primaryKey
on this model. It... wasn't a standard incrementing model. It's primary key was based on another table and it was only a 0-or-1 to 1 mapping anyway.
Moral of the story: make sure you test the serializability of your models!
Continue reading...