ian_h's avatar

ian_h wrote a reply+100 XP

4d ago

I would replace optimised with streamlined maybe.. IMO, leans more on the "convenience" grammar than "technically tweaked" as such.

While I'm in the docker pit for development and wouldn't develop PHP natively in windows (been there, done that 20+ years ago).. I can see the appeal of this kind of setup for people.. let's face it, even with a myriad tools for building your own stacks (of which you can easily "template"), people love herd and valet etc.. so there's a market for it.. especially if it's free and can compete with herd's paid-for features.

Even that aside.. if it's been a technical challenge for you and and offers self-achievement, then I'd say that's a win and a bonus if it becomes more widely adopted πŸ‘πŸ»

Good luck with the venture! πŸ™‚

ian_h's avatar

ian_h wrote a reply+100 XP

5d ago

They were on some kind of drive to turn Laravel PHP stuff into JS it felt like at one point, especially with Pest and Volt.. both of which, IMO, are a royal PITA and a mess to work with.

ian_h's avatar

ian_h wrote a reply+100 XP

1mo ago

Also, I'd highly recommend not using env() in the code, rather Config::get() as the former will cause you issues when you cache the config in production.

ian_h's avatar

ian_h wrote a reply+100 XP

1mo ago

I find the One Time Operations a nice package for handling these "one-offs" as they work like migrations, can run them async of synchronously out of the box and means you keep the migrations for what they're designed for, schema definitions.

ian_h's avatar

ian_h wrote a reply+100 XP

2mos ago

Sounds like Lary should be renamed 'Clippy' with an opening line like that!

ian_h's avatar

ian_h started a new conversation+100 XP

2mos ago

@JeffreyWay I posted a reply in the comments on this video (it was a reply to the reply to my OP). The comments count says 4 but there's only 3 visible.. seems my 2nd post there is hidden somewhere?

ian_h's avatar

ian_h wrote a comment+100 XP

2mos ago

@jonmbt It certainly was a test, that's for sure! I know it's not the same now.. but it was also waaay before we had the likes of Carbon for a nicer date API (not that it really mattered.. that would only help so much).

I think it was housed in Typo3 so that CMS features for internationalisation could also be offered, as the same company also had European branches too (though I think the US company seems to have just adopted the first part of the name now). Luckily, the Euro sites were far less dictating in what/how we stored the data. As long as it functioned how it was meant to, and we could generate reports and appointments in human-readable format, the rest was down to us... thankfully!

The scary part was, is that the DB was accessed by humans, as we also had to make a phpMyAdmin instance available for the US too..... yeah... would could possibly go wrong with that.... more than once? (luckily, I didn't have to do too much in the way of sysadmin there).

ian_h's avatar

ian_h wrote a comment+100 XP

2mos ago

I like that... "store in the DB as UTC, no exceptions!"

My worst nightmare was developing a booking funnel application for a well-known auto glass replacement company in the US. The development and hosting was in the UK, but the company insisted that the date/time info was stored in the EST timezone due to HQ being in Columbus, Ohio IIRC, and it had to be "humanly readable" in the database too (no unix timestamps etc... why? your guess is as good as mine!).

This was an absolutely massive nightmate having to do conversions between the dates/times with the server set to UTC.... but then of course, there was something like 48 or so States to cover too... a couple of which I found out at the time don't follow summer time changes either. This was back in ~2009.. the application was written using ZendFramework, that was hosted inside of a "page" component in Typo3... I'll leave the rest to your imagination πŸ˜ƒ

ian_h's avatar

ian_h started a new conversation+100 XP

2mos ago

It seems when comments (largely spam) are removed from a thread, the thread still remains as a recently updated thread, when in reality, it's not if it's had the last record removed.

Would be great to see the date reset to the last comment date so that 2 year old (for example) threads don't appear in the latest updated threads.

This thread for example: https://laracasts.com/discuss/channels/general-discussion/good-video-service-for-an-education-saas

Last post was 2 years ago. A spam post was removed today, but it's listed in the forum index as 'last updated 8h ago'. Yes, it was, if you treat the last updated timestamp as a literal action (removal of the post) but it actually has no correlation with the thread content and shouldn't be listed as a recent thread.

ian_h's avatar

ian_h wrote a reply+100 XP

3mos ago

Tailwind 4 moved away from the tailwind.config.js file (don't know if it still gets read, but the auto-updater will remove it). Everything is defined in the CSS file now.

In one of my recent projects, I defined this as:

typography.css contains:

It doesn't use external fonts, but maybe the rest of the setup is the same.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

For me, it works really well as for 99% of projects, even the reverse proxy configs are the same less for the domain/log names/ssl certs etc. This mostly only deviates for me if I have a project that also uses reverb for websockets, where I need to define another section to handle those.

Bear with me.. the projects and config for these are all in private repos.. and the final docker container is built through github actions.. but I'll throw up a dummy repo that has these configs in them and share the link later today.. bit too much to start copy/pasting here.

The only "gotchya" I found with this originally was when working with Inertia-based projects and hitting the assets because the docker container only runs as HTTP, the certs all sit in the reverse proxy for HTTPS. In AppServiceProvider, I originally had just:

if ($this->app->environment('production')) {
    URL::forceScheme('https');
}

But for Inertia apps, I also had to add:

$this->app['request']->server->set('HTTPS', true);

I added this under the first line to scope it into the production environment as locally, everything is just HTTP.

I'll get a repo up later.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

We encountered this probably a decade ago at a company I used to work at and ever since, I've personally opted to "break conventions" for my own projects and for production, bundle nginx with my php-fpm container for this very reason. This then sits behind a host-installed nginx working as a reverse proxy.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

@glukinho You can do this with the mklink command:

mklink /?
Creates a symbolic link.

MKLINK [[/D] | [/H] | [/J]] Link Target

        /D      Creates a directory symbolic link.  Default is a file
                symbolic link.
        /H      Creates a hard link instead of a symbolic link.
        /J      Creates a Directory Junction.
        Link    Specifies the new symbolic link name.
        Target  Specifies the path (relative or absolute) that the new link
                refers to.

A decent explanation for the difference between dir symlinks and junctions can be seen on superuser.com.

ian_h's avatar

ian_h was awarded Best Answer+1000 XP

4mos ago

Consistency in whether you're returning a collection of users, or a collection of books, the structure is as similar as possible, the same as single entitity responses.

The message here is quite common, and can contain various things (we use it at current $dayJob for returning messages to the FE to display so the languages are handled in the backend).. but it's not mandatory in any shape or form.

Get all users response

{
    "message": "Users list",
    "data": [
        {
            "uuid": "e327e77c-443d-43e5-9d92-08bff6348885",
            "username": "MyUsername",
            "first_name": "Joe",
            "last_name": "Bloggs",
            "email": "[email protected]"
        },
        {
            "uuid": "87a518f2-2538-4162-8b94-2733e4c8ddca",
            "username": "ANotherUsername",
            "first_name": "Sally",
            "last_name": "Smith",
            "email": "[email protected]"
        }
    ],
    "pagination": {
        "previous": "/users?page=1",
        "next": "/users?page=3",
        "page": 2,
        "total": 6
    }
}

Single user response

{
    "message": "Single user",
    "uuid": "e327e77c-443d-43e5-9d92-08bff6348885",
    "username": "MyUsername",
    "first_name": "Joe",
    "last_name": "Bloggs",
    "email": "[email protected]"
}

All books response

{
    "message": "Books list",
    "data": [
        {
            "uuid": "7dc3ca73-7766-4ca4-a076-2bd5fb375b6d",
            "title": "Best Book Ever! 2025",
            "author": "John Jones",
            "cover_image": "/book/7dc3ca73-7766-4ca4-a076-2bd5fb375b6d/cover.jpg"
        },
        {
            "uuid": "d2ec3943-3a38-46a7-ab19-701fe5e72989",
            "title": "Vintage Book",
            "author": "Michelle Evans",
            "cover_image": "/book/d2ec3943-3a38-46a7-ab19-701fe5e72989/cover.jpg"
        }
    ],
    "pagination": {
        "previous": null,
        "next": "/users?page=2",
        "page": 1,
        "total": 50
    }
}

Single book response

{
    "message": "Single book",
    "uuid": "7dc3ca73-7766-4ca4-a076-2bd5fb375b6d",
    "title": "Best Book Ever! 2025",
    "author": "John Jones",
    "cover_image": "/book/7dc3ca73-7766-4ca4-a076-2bd5fb375b6d/cover.jpg"
}

The idea here is that a list of users or a list of books, despite having different properties for the models, the response is consistent:

  • Both contain a message field with similar content
  • Both contain a data element wrapping around multiple entities
  • Both contain a pagination object

The same is said for the single entities:

  • Both contain the message field with similar content
  • Both contain a single object not wrapped in a data property
  • Neither contain pagination (obviously not needed for single entities)

Although the property names are different between the entity types, the response expectations are consistent with each other. This makes using the API much simpler from any client (be it a web front end, mobile app, etc).

Example of a bad design, as we're refactoring at $dayJob for example, is that when the internal API was first built, some responses had source instead of data, some did use data, some used the name of what the collection contained, users for example.. this highlights inconsistency as you don't know what a response is going to contain, it's different, randomly, for various endpoints.

Hope this clarifies some things.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

Consistency in whether you're returning a collection of users, or a collection of books, the structure is as similar as possible, the same as single entitity responses.

The message here is quite common, and can contain various things (we use it at current $dayJob for returning messages to the FE to display so the languages are handled in the backend).. but it's not mandatory in any shape or form.

Get all users response

{
    "message": "Users list",
    "data": [
        {
            "uuid": "e327e77c-443d-43e5-9d92-08bff6348885",
            "username": "MyUsername",
            "first_name": "Joe",
            "last_name": "Bloggs",
            "email": "[email protected]"
        },
        {
            "uuid": "87a518f2-2538-4162-8b94-2733e4c8ddca",
            "username": "ANotherUsername",
            "first_name": "Sally",
            "last_name": "Smith",
            "email": "[email protected]"
        }
    ],
    "pagination": {
        "previous": "/users?page=1",
        "next": "/users?page=3",
        "page": 2,
        "total": 6
    }
}

Single user response

{
    "message": "Single user",
    "uuid": "e327e77c-443d-43e5-9d92-08bff6348885",
    "username": "MyUsername",
    "first_name": "Joe",
    "last_name": "Bloggs",
    "email": "[email protected]"
}

All books response

{
    "message": "Books list",
    "data": [
        {
            "uuid": "7dc3ca73-7766-4ca4-a076-2bd5fb375b6d",
            "title": "Best Book Ever! 2025",
            "author": "John Jones",
            "cover_image": "/book/7dc3ca73-7766-4ca4-a076-2bd5fb375b6d/cover.jpg"
        },
        {
            "uuid": "d2ec3943-3a38-46a7-ab19-701fe5e72989",
            "title": "Vintage Book",
            "author": "Michelle Evans",
            "cover_image": "/book/d2ec3943-3a38-46a7-ab19-701fe5e72989/cover.jpg"
        }
    ],
    "pagination": {
        "previous": null,
        "next": "/users?page=2",
        "page": 1,
        "total": 50
    }
}

Single book response

{
    "message": "Single book",
    "uuid": "7dc3ca73-7766-4ca4-a076-2bd5fb375b6d",
    "title": "Best Book Ever! 2025",
    "author": "John Jones",
    "cover_image": "/book/7dc3ca73-7766-4ca4-a076-2bd5fb375b6d/cover.jpg"
}

The idea here is that a list of users or a list of books, despite having different properties for the models, the response is consistent:

  • Both contain a message field with similar content
  • Both contain a data element wrapping around multiple entities
  • Both contain a pagination object

The same is said for the single entities:

  • Both contain the message field with similar content
  • Both contain a single object not wrapped in a data property
  • Neither contain pagination (obviously not needed for single entities)

Although the property names are different between the entity types, the response expectations are consistent with each other. This makes using the API much simpler from any client (be it a web front end, mobile app, etc).

Example of a bad design, as we're refactoring at $dayJob for example, is that when the internal API was first built, some responses had source instead of data, some did use data, some used the name of what the collection contained, users for example.. this highlights inconsistency as you don't know what a response is going to contain, it's different, randomly, for various endpoints.

Hope this clarifies some things.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

There is no "standard" for JSON responses (unless you go with the likes of the JSON:API), so really it's down to you to decide what you want from the response.

The biggest tip I would give, is to make all responses consistent and to use HTTP status codes correctly.

I would say skip the often used status property that I see added in many APIs unfortunately, this is what the HTTP status code should be defining. I do normally at least stick with the "standard" convention of wrapping collections of data in a data property, and skip this for single entities (ie: a call to an index route where there might be one or more items, wrap it, a show route where there is only one, I tend not to).

Also depends if you want to include more data in a single response. JSON:API keeps things "light" as such, but means there's multiple requests to make for the likes of relationships, where as you might decide the best approach for your case is to include relationships in your initial response.. again, this all depends largely on the data you're returning and how you decide you want to architect it, there is no "right or wrong" way to do that, as long as you're consistent.

Creations and updates are a little different as these will largely depend on the model definitions with regards to properties/fields and unless you're allowing bulk creations/edits, you're only going to be dealing with single entities at a time.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

Looks like recent github contributions (as recent as 3rd December).. so looks like he's still kicking about πŸ™‚

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

@jlrdw I read somewhere (I think) they changed a bunch of auth/controller stuff, which does away with the auth routes as they used to be included and the likes of LoginController and RegisterController etc.. not sure what benefit that brings tbh.

Sometimes, I get the impression they're "bored".. but have to be seen to be making some kind of change to maintain "progression".... sometimes, something is simply.............. done.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

Fair enough. I've not tried any of the new starter kits as they don't interest me.. but wonder if a specific version can be pulled into a new project with a composer install?

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

If you're looking for the likes of Breeze or Jetstream, these are still possible (I use Breeze normally, even with Laravel 12).

If this is what you're after, use the pre-12 methods, using Breeze as an example:

  • Create Laravel project
  • composer req laravel/breeze --dev
  • php artisan breeze:install
ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

I've not played with htmx, but with your knowledge/experience of LW and Vue.. I'd probably do this with AlpineJS instead as you've likely used this with LW and has a similar syntax to Vue and both light weight and flexible enough to do what you need.

ian_h's avatar

ian_h wrote a reply+100 XP

4mos ago

Yup.. as the Op and friend aren't using docker, this would be the best alternative.

WSL was such a great addition to the windows world IMO. I certainly don't miss dual-booting or heavy VMWare+Vagrant setups and even beyond the application side of things.. just being able to use a linux shell for things was a huge step forward over windows console or cygwin.

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

If you're not using docker to share environment setups, I would strongly recommend that your friend uses WSL.. you'll both be running on Linux then (and pcntl will be available to said friend) and he can keep using his windows box for the IDE etc etc.

Although I use docker to run my apps in (still within WSL), I've not run PHP on windows since 5.1.. it's definitely the best of both worlds (I use windows for PHPStorm and all of the actual dev stuff/code/git etc resides in the WSL side of things).

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

Aaaand back again. This isn't actually a Laravel bug itself, this stems from the phpredis client that is Laravel's default Redis client.

Rebuilding my docker container with the dependencies required by Cachewerk's Relay client and swapping out the REDIS_CLIENT in the .env to use relay instead, this works as expected πŸ˜ƒ

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

Coming back to this... cache tagging isn't something I would rely on, it's just come around to bite me on the arse! It seems to point to an issue with the flush() method if you use that to bulk delete entries rather than a forget() call to a single key.

This fails for example:

Cache::tags(['my-tag'])->put('demo-key-01', 'some-data-01');
Cache::tags(['my-tag'])->put('demo-key-02', 'some-data-02');

Cache::tags(['my-tag'])->flush();

You would expect that both the demo-key-01 and demo-key-02 entries to be removed, but alas, neither are, resulting in a broken cache implementation if you're hoping for bulk removal.

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

I'm based in London, UK.. so granted, internet here is pretty stable (though I do understand your pain as it was only a few years ago that this property had access to something decent, before that, it was 3Mbps/0.8Mbps up/down.. which wasn't fun when your 2 young lads also wanted to stream separate youtube videos πŸ˜ƒ).

I also think that building pre-configured stuff for yourself isn't a bad idea either, especially if it's something you use reguarly (I have multiple pre-configured docker images that I use for myself for pure convenience and then use a Makefile to run various commands inside/outside of the container(s)).

I guess context is key for this too.. it may well in some situations be far easier/more convenient/efficient to download a single docker image than a base image and then a bunch of dependencies.

Good luck with your project πŸ‘πŸ»

ian_h's avatar

ian_h was awarded Best Answer+1000 XP

5mos ago

I'm sure the docs used to mention it somewhere, but if you're going for a containerised approach, you'd run composer in a container too, eg:

docker run --rm \
    -u "$(id -u):$(id -g)" \
    -v $(pwd):/var/www/html \
    -w /var/www/html \
    composer/composer:latest \
    composer req laravel/sail --dev

I haven't installed PHP locally for nearly a decade.. and on the 2 occassions I needed to use Sail, it installed just fine running composer in a container.

While I commend you on looking into writing a package... it's ultimately a solution looking for a problem.

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

I'm sure the docs used to mention it somewhere, but if you're going for a containerised approach, you'd run composer in a container too, eg:

docker run --rm \
    -u "$(id -u):$(id -g)" \
    -v $(pwd):/var/www/html \
    -w /var/www/html \
    composer/composer:latest \
    composer req laravel/sail --dev

I haven't installed PHP locally for nearly a decade.. and on the 2 occassions I needed to use Sail, it installed just fine running composer in a container.

While I commend you on looking into writing a package... it's ultimately a solution looking for a problem.

ian_h's avatar

ian_h wrote a reply+100 XP

5mos ago

I think you should focus on what interests you. The scale/complexity is somewhat immaterial.

My first ever PHP project back in 2001 was a guestbook. It read/wrote to files on my server as it's "database". This was pretty simple.. one message per file, saved in a directory.

My second PHP project was a forum like vBulletin. Vastly more complex, used MySQL as the backend, multi-user accounts, etc. The code, in today's world was horribly insecure and wide open to SQL injection and XSS attacks.

The guestbook in reality would probably have been a better codebase to showcase as there was less to do and it was likely more secure (there was no database, so that removes SQL injection, for example).

Personally, what I look for when hiring people is how they perceive the problem and how they go about a solution. Keep the code clean, simple, maintainable. I'd sooner see a few simple projects of really decent quality than something that looked like it was thrown against a wall to see what sticks.

Complexity will come with experience.. start "small" and focus on the quality of that.

An example... we recently(ish) hired a person that was essentially a junior dev. He showed decent code, on par with what was to be expected with his experience, however, it was the answers to questions that ultimately scored him the position. We weren't looking for a junior per-se, but his answers clearly showed that he understood the problems, how he would go about dealing with those problems, his thought processes and his passion (this is a key quality, far more than technical ability.. I can teach you to write decent code/tests.. I can't teach you to be enthusiastic about what you do)... so I personally really wouldn't worry about complexity if you're looking to start your engineering career.. focus on clean, simple architecture that a company can see would be easy for a team to maintain in a few years time.. that offers them value and therefore, makes you more of an attractive hire, IMO.