Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Smart" route URLs caching #119

Merged
merged 31 commits into from
Dec 24, 2024
Merged

Conversation

Voltra
Copy link
Collaborator

@Voltra Voltra commented Dec 2, 2023

Initial draft of the new route caching system as described in #116

A few quirks need to be ironed out (mainly completely removing any and all traces of the previous route caching system).

The args are mainly here to enable project-dependent customization for generating multiple "independent" URLs per page (e.g. translatable pages)

Copy link

what-the-diff bot commented Dec 2, 2023

PR Summary

  • Enhanced the management of Page URLs
    We have created various classes and traits to streamline the process of generating, storing, updating, and retrieving URLs for Page models. These efforts improve website load time by leveraging an advanced caching mechanism.

  • Introduction of PageRoutesObserver class
    This new class observes events like the creation, update, restoration, and deletion of Page models. It uses the PageRoutesService to freshen the stored URLs for associated pages.

  • Introduction of HandlesPageUrls trait
    Added to the Page model, which contains methods for generating and caching page URLs.

  • Introduction of PageRoutesService class
    This class manages the updating of the stored URL mappings for Page models. It helps enhance the site's performance by keeping the URLs mapped correctly.

  • Upgrade of PageController
    The PageController now uses the PageRoutesService for resolving the page model when a URI is not provided. This change enables more efficient page load times and reduces the chances of page not found errors.

  • Renaming and creation of various methods in HandlesPageUrls trait
    Methods for generating page URLs, fetching available URLs, and getting the default arguments for URL generation have been introduced. Also, the getUriKey method was renamed to getUrlCacheKey, which more aptly describes its functionality.

  • Services indicated in FilamentFabricatorServiceProvider
    The PageRoutesService dependency and other related alterations were added to the FilamentFabricatorServiceProvider, helping in resolving the PageRoutesService instance and updating the page binding.

  • New methods in the PageRoutesService class
    Several methods were created to get the Page associated with a given URI, fetch the URI to ID mapping, get the cached URIs for a specific page, remove old URLs from the cached mappings, and replace the cached ID to URI mapping. These enhancements ensure the URL mappings remain up-to-date, reducing potential errors and improving system efficiency.

@Ray-vV
Copy link

Ray-vV commented Dec 6, 2023

@Voltra Thanks for your work on this firstly! I've added your code to my project and noticed I was getting the following error:

test_error

Then I noticed the method call inside HandlesPageUrls.php

    public function getAllUrls(): array {
        return array_map([$this, 'getUrl'], $this->getAllUrlCacheKeysArgs());
    }

The call is to $this->getAllUrlCacheKeysArgs(), but that method doesn't exist in the trait.

After changing this call to $this->getAllCacheKeyArgs(), this problem was fixed for me.

 public function getAllUrls(): array {
        return array_map([$this, 'getUrl'], $this->getAllCacheKeyArgs());
    }

I will start testing the code inside my project, so if you'd like additional feedback I could provide that.

@Ray-vV
Copy link

Ray-vV commented Dec 6, 2023

See #1

@Voltra
Copy link
Collaborator Author

Voltra commented Dec 6, 2023

Whoops, that's what I get for using github codespaces instead of my usual setup, will correct the typo

@Voltra Voltra marked this pull request as ready for review December 18, 2023 22:05
public function getUrl(array $args = []): string {
$cacheKey = $this->getUrlCacheKey($args);

//TODO: Clear and re-compute cached routes when the routing prefix changes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this maybe we can introduce an artisan command? then in the docs mention that if routing prefix changes, run the command to clear and re-compute all routes?

*/
public function deleted(Page $page): void
{
//TODO: implement this
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should deleted case be handled?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now it seems unclear. There's no way to have a "re-attach to root" strategy, since there's no concept of root. If we still have the page's relationship data, we could use a linked-list inspired deletion procedure (attaching the parent of the children of this page to be this page's parent)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the deleting method for that implementation

@Z3d0X

This comment was marked as resolved.

@Voltra
Copy link
Collaborator Author

Voltra commented Jan 11, 2024

I've tried to keep the exact same interface, but just change the implementation itself. The only breaking change is in the manager, which now has a parameter to its constructor. A temporary solution would be to make it nullable (defaulting to null would keep the same arity) and using resolve if the provided value is null

@Z3d0X
Copy link
Owner

Z3d0X commented Jan 12, 2024

If you could work out locale support from #75 (comment), I'd be happy to make a 3.x release

@Voltra
Copy link
Collaborator Author

Voltra commented Jan 13, 2024

If you could work out locale support from #75 (comment), I'd be happy to make a 3.x release

I think it's gonna be difficult and/or tricky to make it 1st-party.

I think it should be a project-dependent configuration of the Page model class. Especially since it heavily relies on how the translation itself is made.

For instance:

namespace App\Models\Overrides;

use Filament\Facades\Filament;
use Filament\SpatieLaravelTranslatablePlugin;
use Spatie\Sitemap\Contracts\Sitemapable;
use Spatie\Sitemap\Tags\Url;
use Spatie\Translatable\HasTranslations;

class Page extends \Z3d0X\FilamentFabricator\Models\Page implements Sitemapable {
    use HasTranslations;

    public array $translatable = [
        'title',
        'slug',
        'blocks',
    ];

    protected $table = 'pages';

    /**
     * {@inheritDoc}
     */
    public function getDefaultUrlCacheArgs(): array
    {
        return [
            'locale' => 'fr',
        ];
    }

    /**
     * {@inheritDoc}
     */
    public function getUrlCacheKey(array $args = []): string
    {
        $keyArgs = collect($this->getDefaultUrlArgs())->merge($args)->all();
        $locale = $keyArgs['locale'];
        $id = $this->id;

        return "page-url--{$locale}--{$id}";
    }

    /**
     * {@inheritDoc}
     */
    public function getAllUrlCacheKeysArgs(): array
    {
        /**
         * @var SpatieLaravelTranslatablePlugin $translatable
         */
        $translatable = Filament::getPlugin((new SpatieLaravelTranslatablePlugin())->getId());

        return array_map(fn (string $locale) => [
            'locale' => $locale,
        ], $translatable?->getDefaultLocales() ?: ['fr', 'en']);
    }

    public function toSitemapTag(): Url|string|array
    {
        $entry = Url::create(url($this->getUrl([
            'locale' => $this->getLocale(),
        ])));

        $argsSets = $this->getAllUrlCacheKeysArgs();

        array_walk($argsSets, function (array $args) use ($entry) {
            $url = $this->getUrl($args);
            $entry->addAlternate(url($url), $args['locale']);
        });

        $entry->setLastModificationDate($this->updated_at)
            ->setChangeFrequency(Url::CHANGE_FREQUENCY_DAILY);

        return $entry;
    }
}

In my case, the locale detection/switching is made via URLs using a custom middleware:

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;

class LocaleDetector
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, \Closure $next): Response
    {
        $path = $request->decodedPath();

        if ($this->matchesLocale($path, 'en')) {
            App::setLocale('en');
        } else {
            App::setLocale(App::getFallbackLocale());
        }

        return $next($request);
    }

    protected function matchesLocale(string $uri, string $locale): bool
    {
        return $uri === $locale
            || str_starts_with($uri, "{$locale}/")
            || str_starts_with($uri, "/{$locale}/");
    }
}

Not all cases will work the same way. You might want to have the locale in the session, or the user's settings. You might need more arguments to generate a route, etc...

That's why these customization/extension points exist in the first place:

  • getDefaultUrlCacheArgs
  • getUrlCacheKey
  • getAllUrlCacheKeysArgs

@Z3d0X Z3d0X changed the base branch from 2.x to 3.x January 18, 2024 04:26
@Z3d0X Z3d0X changed the base branch from 3.x to 2.x January 18, 2024 04:34
@Z3d0X
Copy link
Owner

Z3d0X commented Jan 18, 2024

I think it's gonna be difficult and/or tricky to make it 1st-party.

I think it should be a project-dependent configuration of the Page model class. Especially since it heavily relies on how the translation itself is made.

Agreed. I think a pinned post on discussions on how this plugin can be extended to support translations should suffice.

@Voltra if there isn't anything else to be added I'm merging this. I have reviewed the code once again, you are right, there doesn't seem to be any breaking changes

@Voltra
Copy link
Collaborator Author

Voltra commented Feb 9, 2024

There were a few bugs that the unit tests help catch

@Voltra
Copy link
Collaborator Author

Voltra commented Feb 15, 2024

Things should be A-OK @Z3d0X

I've also added a config option that hooks into existing laravel artisan commands to clear and refresh the caches at the same time as the others.

@Voltra
Copy link
Collaborator Author

Voltra commented Feb 27, 2024

Just got done fixing a corner-case in updates where changing the parent page would not properly change the URLs. Now all cases should be handled properly and thoroughly tested.

@Voltra Voltra requested a review from Z3d0X March 3, 2024 12:55
@Voltra
Copy link
Collaborator Author

Voltra commented Jun 7, 2024

Should be good to merge

@panakour
Copy link

any update on this?

@Z3d0X Z3d0X closed this Dec 22, 2024
@Voltra
Copy link
Collaborator Author

Voltra commented Dec 22, 2024

Any chance to get this merged?

@Ray-vV
Copy link

Ray-vV commented Dec 24, 2024

Any chance to get this merged?

Seconded. Also, it has been over a year and closing it without any kind of feedback or response after all of @Voltra 's work seems kind of distasteful.

@Z3d0X
Copy link
Owner

Z3d0X commented Dec 24, 2024

@Ray-vV @Voltra This adds a bit of a complexity to the plugin. I'm not entirely confident, taking up the burden of maintaining such complexity.

That being said, considering how much @Voltra has invested in this, I'd like to invite @Voltra as a maintainer of this plugin. That way this can be merged as well.

@Voltra Voltra reopened this Dec 24, 2024
@Voltra
Copy link
Collaborator Author

Voltra commented Dec 24, 2024

I think it has been community-tested by @Ray-vV in the past. My unit tests should cover most (if not all the) cases. If it's all OK, I'm fine with merging and helping in maintenance of the project (especially that part).

If any issue arises post merge, I'll be more than happy to help resolve it.

@Z3d0X
Copy link
Owner

Z3d0X commented Dec 24, 2024

@Voltra awesome let's go ahead with it

@Z3d0X Z3d0X merged commit 876906d into Z3d0X:2.x Dec 24, 2024
7 checks passed
@raheelms
Copy link

raheelms commented Dec 29, 2024

i am getting this error when trying to clear config, route etc. i don't know how to solve it.
All of a sudden I am getting this error after composer update.


at vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php:87
   83▕     public function removeUrlsOf(Page $page): void
   84▕     {
   85▕         // First remove the entries from the (ID -> URI) mapping
   86▕         $idToUrlsMapping = $this->getIdToUrisMapping();
➜  87▕         $urls = $idToUrlsMapping[$page->id];
   88▕         $idToUrlsMapping[$page->id] = null;
   89▕         unset($idToUrlsMapping[$page->id]);
   90▕         $this->replaceIdToUriMapping($idToUrlsMapping);
   91▕

1   vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php:87
    Illuminate\Foundation\Bootstrap\HandleExceptions::Illuminate\Foundation\Bootstrap\{closure}("Undefined array key 1", "C:\xampp\htdocs\nextxlfabricator\vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php")

2   vendor\z3d0x\filament-fabricator\src\Commands\ClearRoutesCacheCommand.php:47
    Z3d0X\FilamentFabricator\Services\PageRoutesService::removeUrlsOf(Object(Z3d0X\FilamentFabricator\Models\Page))```

@Voltra
Copy link
Collaborator Author

Voltra commented Dec 30, 2024

i am getting this error when trying to clear config, route etc. i don't know how to solve it. All of a sudden I am getting this error after composer update.

Thank you @raheelms for the report! I'll investigate!

Any more pieces of context would be useful!

EDIT: Will take the rest of the conversation to #198

@raheelms
Copy link

i am getting this error when trying to clear config, route etc. i don't know how to solve it. All of a sudden I am getting this error after composer update.

Thank you @raheelms for the report! I'll investigate!

Any more pieces of context would be useful!

EDIT: Will take the rest of the conversation to #198


  at vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php:87
     83▕     public function removeUrlsOf(Page $page): void
     84▕     {
     85▕         // First remove the entries from the (ID -> URI) mapping
     86▕         $idToUrlsMapping = $this->getIdToUrisMapping();
  ➜  87▕         $urls = $idToUrlsMapping[$page->id];
     88▕         $idToUrlsMapping[$page->id] = null;
     89▕         unset($idToUrlsMapping[$page->id]);
     90▕         $this->replaceIdToUriMapping($idToUrlsMapping);
     91▕

  1   vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php:87
      Illuminate\Foundation\Bootstrap\HandleExceptions::Illuminate\Foundation\Bootstrap\{closure}("Undefined array key 6", "C:\xampp\htdocs\nextxlfabricator\vendor\z3d0x\filament-fabricator\src\Services\PageRoutesService.php")

  2   vendor\z3d0x\filament-fabricator\src\Commands\ClearRoutesCacheCommand.php:47
      Z3d0X\FilamentFabricator\Services\PageRoutesService::removeUrlsOf(Object(Z3d0X\FilamentFabricator\Models\Page))```

@Voltra Voltra deleted the feature/SMART_ROUTE_CACHING branch January 7, 2025 20:50
@Voltra Voltra restored the feature/SMART_ROUTE_CACHING branch January 7, 2025 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants