Account created on 20 July 2009, over 15 years ago
#

Merge Requests

Recent comments

🇷🇺Russia Chi

@nicxvan we probably need to understand why "Implements hook_*" comment was initially added to Drupal coding standars. I guess it is needed to distinguish ordinary PHP functions from functions that implement Drupal hooks. Obviously with the Hook attribute is no longer needed. Also api.drupal.org might use this notation when listing hook implementatations.

For me it seems that in most cases docblock in hooks is not needed at all.

This issue is very similar to #3376518: Allow omitting @var, @param and @return tags or their descriptions if the variable name and type is enough . Maybe we should take same approach once it is resolved.

🇷🇺Russia Chi

Re: #292 drush gen phpstorm-meta will be able to generate these completions in the next release.

🇷🇺Russia Chi

Cross posted from 📌 OOP hooks using event dispatcher Needs review . The "implements" seems redundant now.

/**
 * Implements hook_help().
 */
#[Hook('help')]
Do you think that "implements" note is redundant for this kind of hooks?

It gets worse in single hook classes.

/**
 * Implements hook_help().
 */
#[Hook('help')]
final class Help {

  /**
   * Implements hook_help().
   */
  public function __invoke(): void {

  }

}
🇷🇺Russia Chi

Certain hook groups should be in a class together.

I would force using single class hooks wherever it is possible. There very few use cases when hooks are highly related to each other.

🇷🇺Russia Chi
/**
 * Implements hook_help().
 */
#[Hook('help')]

Do you think that "implements" notes are redundant for this kind of hooks?

🇷🇺Russia Chi

The fix seems trivial.

Note that @inheritdoc in \Drupal\entity\Access\EntityRevisionRouteAccessChecker::access is not actually referencing anything.

🇷🇺Russia Chi

Drupal currently does not support image styles for files stored in extension directories.

🇷🇺Russia Chi

That is the time format that all datetime fields expect.

Because it's storage format. It is rarely used in UI.
You may make the method more flexible by adding optional format parameter like follows.

function getNow(string $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT)
Is that a necessity?

It's a general reason for having services injected. Core uses @datetime service when needs to obtain current time.

Hopefully, someday it'll switch from the custom baked TimeInterface to PSR-20.

🇷🇺Russia Chi

I am thinking of making all TT services final to avoid such issues in feature.

🇷🇺Russia Chi

Changing constructor signature could break existing sites that extends these services. Though the should not be considered as public API.
Anyway, I propose we change this the next major release. I've just created 4.x branch.

🇷🇺Russia Chi

Why are the cases coded in camel case? They are like constants. Shouldn't we use uppercase for them?

🇷🇺Russia Chi

I've just released a module that implements the date/time fields in a different way.
It also supports HTML5 elements in views exposed forms
https://www.drupal.org/project/date_point

🇷🇺Russia Chi

There are a few concerns with the proposed approach.

1. The implementation is hardcoded to one specific date format (Y-m-d\TH:i:s).
2. It overlaps with PSR 20 where now() is supposed to return DateTimeImmutable object.
3. It does not use core time service. So no way to mock the date in tests.

🇷🇺Russia Chi

Meanwhile, Date Point's build is green again. Has something been already changed?
https://git.drupalcode.org/project/date_point/-/pipelines/359856

🇷🇺Russia Chi

I like the idea of "Golden Contrib" which was proposed years ago
https://www.drupal.org/project/drupal/issues/1255674#comment-4917546

It's basically a curated list of Drupal modules and themes.
For each project in this list DA must ensure that
- It has active maintainer
- It has a stable release compatible with the current Drupal core
- It has proper documentation
- It complies with the strategy of Drupal CMS

🇷🇺Russia Chi

DomDocument is still pretty verbose.

DomDocument was referenced mainly as an example. I don't think it's the right tool for this. See note #6.

I was looking for something Yii HTML but with support of attachments and custom render callbacks.

🇷🇺Russia Chi

I think such a big change deserves a change record and 11.1 release highlights.

🇷🇺Russia Chi

What was the reason that 'label_count' is moved away from other 'label_*' parameters?

🇷🇺Russia Chi

All we need is a mechanism for the reset, that does not pollute the public interface.

If we were using the Symfony core, we would already have such a mechanism.

Anyway, I suppose it's not a big deal to enable this service pass in Drupal kernel.
\Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass

🇷🇺Russia Chi

unless they use some kind of magic plugin that reads the services.yml files

For PhpStorm you can generate meta information about services.

drush gen phpstorm-meta

Psalm also reads it. Not sure about PhpStan. I suppose phpstan-drupal can help on this.

🇷🇺Russia Chi

#2602368: Allow objects that implement RenderableInterface to used interchangeably with render arrays. indeed is very close to this one. Though the proposed approach is a bit different.

Here we aim to completely remove render arrays.
In #2602368 the render arrays will remain as intermediate layer between elements (objects) and renderer service. That is much easier to implement. The submitted patch is actually a tree-lines change.

As of BC, we don't have to implement it imminently. Custom projects can benefit from this change right now. Contributed projects may start using element objects in major releases where BC breaks are allowed.

🇷🇺Russia Chi

I think this issue has wider scope than 🌱 [policy] Standardize how we implement in-memory caches Needs work . Application state is not only about static caches. There are some other resources that may need to be reset between requests. DB connections, temporary files, open streams, etc. In Spiral framework that process is called finalizing. Finalizers are responsible for cleaning up resources. For instance, logger finalizer will call reset method on Monolog loggers.

Besides resource management there is another challenge. Request aware services. In core.services.yml I counted 47 services that has direct dependency on request_stack and there are many services that has indirect dependency through services like @current_route_match, current_user, datetime.time, etc. In Spiral that implemented with "scoped containers". For each request it creates a scoped container derived from root container with some request specific services.

One more issue to address is stateful Drupal kernel. It has a few of boolean flags (booted, prepared, etc) that needs to be reset after each request.

🇷🇺Russia Chi

It takes 0.015 ms on my localhost. That file is likely loaded from opcache and Composer caches the array in static variable. So it's Ok to call this method multiple times.

🇷🇺Russia Chi

@oily The are dozens different render elements in Drupal core. The MR currently covers just a few ones.

🇷🇺Russia Chi

A bit surprised how much this constant is used in Drupal.

🇷🇺Russia Chi

Submitted a MR for plain text formatter.

I don't know how is JsonMarkup supposed to work here.

🇷🇺Russia Chi

Turns out that default "Plain text" formatter has same bug.

🇷🇺Russia Chi

Given that expressions are not allowed in class constants we will have to deprecate that constant in favor of static method.

public static function version():string {
  return InstalledVersions::getPrettyVersion('drupal/core');
}
🇷🇺Russia Chi

We may use getPrettyVersion() instead.

echo Composer\InstalledVersions::getVersion('drupal/core'), \PHP_EOL;
echo Composer\InstalledVersions::getPrettyVersion('drupal/core'), \PHP_EOL;

Output on 10.3.10

10.3.10.0
10.3.10

Output on 11.x

11.9999999.9999999.9999999-dev
11.x-dev
🇷🇺Russia Chi

@oily that one was about setting Drupal version to 11.2-dev for 11.x branch. That's exactly what MR in this issue does. I am fine to close 🐛 Update Drupal::VERSION in dev branch Active as duplicate.

The relevant Slack conversation:
https://drupal.slack.com/archives/C1BMUQ9U6/p1729010405763549

Note that we will have to update that version to 11.3-dev once 11.3.x branch is open for development.

🇷🇺Russia Chi

Re #10

By "documentation," I mean a list of available properties with their types and a brief description. This documentation may already exist somewhere, but you would need to look it up yourself. For objects with typed properties and methods, IDE can provide that information for you. See Use the builder pattern to make it easier to create render arrays Needs work for details.

How do we handle BC, especially with alter hooks that expect to find arrays?

I've no idea at this moment. Another hook? Anyway, this needs some brainstorming.

🇷🇺Russia Chi

Re #8 That make sense.

Technically, the Drupal already has sort of DOM based on render arrays (should we call it DAM?). The issue merely about replacing associative arrays with objects.

it's better to create a lightweight element tree with a few helper methods to simplify manipulation and traversal

nicmart/tree seems like the right tool for this, at first glance.

🇷🇺Russia Chi

Re #5
That requires benchmarks. My guess is that manipulating an object tree costs almost nothing compared to other operations performed by Drupal's theme layer, such as preprocessing variables and rendering Twig templates.

🇷🇺Russia Chi

Actually, I think, having a full-fledged DOM implementation is not needed. We only need DOM as backing store for content tree. From this point, it's better to create a lightweight element tree with a few helper methods to simplify manipulation and traversal.

PHP’s built-in DOM extension feels overly complex for such tasks and has some pitfalls when dealing with multiple DOMDocument objects. On the other hand, it offers some cool features that could be nice to have, such as traversing in the upward direction, XPath, and CSS selectors (introduced in PHP 8.4) for finding elements. However, none of these features are essential for Drupal's rendering system.

🇷🇺Russia Chi

Overall, this will only mitigate the problem. Dropping render arrays is the only way to fully resolve the issue. I’ve filed a ticket in the ideas queue.

🇷🇺Russia Chi

Meanwhile, we can use a render element to gradually migrate from render arrays to DOM.

/**
 * Provides a render element to build markup using document object model.
 *
 * Properties:
 * - #dom: Document Object Model
 *
 * Usage Example:
 * @code
 * $build['dom'] = [
 *   '#type' => 'dom',
 *   '#dom' => $dom,
 * ];
 * @endcode
 *
 * @RenderElement("dom")
 *
 * @see https://www.php.net/manual/en/book.dom.php
 */
final class Dom extends RenderElementBase {

  /**
   * {@inheritdoc}
   */
  public function getInfo(): array {
    return [
      '#pre_render' => $this->preRenderEntityElement(...),
      '#dom' => NULL,
    ];
  }

  /**
   * Pre-render callback.
   */
  public static function preRenderEntityElement(array $element): array {
    $dom = $element['#dom'] ?? NULL;
    if (!$dom instanceof \DOMDocument) {
      throw new \InvalidArgumentException('#dom property must contain instance of DOMDocument');
    }

    // Instead of using saveHtml, it should traverse the tree recursively,  
    // rendering child nodes while respecting theme hooks and bubbling attached  
    // metadata. 
    return ['#markup' => $dom->saveHTML()];
  }

}
🇷🇺Russia Chi

PHP has evolved a lot since this issue was created. Type hints, property promotion, named arguments, etc. So instead of builders we can create value objects. It'll give same DX in modern IDEs.

Builder

$variables['table'] = TableElement::create()
  ->setHeader($headers)
  ->setRows($rows)
  ->setAttributes(['id' => $table_id])
  ->setTableDrag([
    [
      'action' => 'order',
      'relationship' => 'sibling',
      'group' => $weight_class,
    ],
  ],
)->toRenderArray();

Constructor

$variables['table'] = new TableElement(
  header: $headers,
  rows: $rows,
  attributes: ['id' => $table_id],
  tableDrag: [
    [
      'action' => 'order',
      'relationship' => 'sibling',
      'group' => $weight_class,
    ],
  ],
)->toRenderArray();
🇷🇺Russia Chi

We can't require write permissions to a PHP file in production.

For Twig files it was resolved in #2544932: Twig should not rely on loading php from shared file system . Can it be done same way for containers?

Anyway DB based container cache may still exist and used as fallback for sites that need container rebuild in non CLI SAPI. The point is to use more efficient solution by default.

🇷🇺Russia Chi

implies opening DB connection

Opening DB connection is rather expensive operation, especially in PostgreSQL. Even if the cache is managed through in memory backend (Redis/Memcache) Drupal will open DB connection as bootstrap container depends on Database service.

🇷🇺Russia Chi

Re #99. That makes sense. Static cache is the fastest cache ever. It would be not wise to throw it away. I think the potential performance gain from static cache might be comparable with the one from skipping bootstrap phase. The application needs to have a control on what needs to be reset.

🇷🇺Russia Chi

Wonder if it's better to create a new cache backend.

🇷🇺Russia Chi

I did some benchmarking locally. Getting cached items back is almost instance when using VarExporter.

Here are some results of profiling Drupal site with cache.container implemented with PhpBackend

Here are the results:



For local Drupal 11 installation the performance boost was about 5-10% for anonymous visitors.

🇷🇺Russia Chi

I didn't expect that someone still uses it.

🇷🇺Russia Chi

Still trying to comprehend how this event loop will work with new MySQL driver ( 📌 [PP-1] Create the database driver for MySQLi Postponed ). As I understand the revolt/event-loop is based on streams. stream_select is essentially a backbone of its async operations. That means the DB driver should be implemented through PHP streams like amphp/mysql.
Did I miss something?

🇷🇺Russia Chi

This would all happen in the non-async database connection

I meant a single HTTP request without any concurrency. In that case in non-async database all queries will happen sequentially. So no locks expected.

🇷🇺Russia Chi

I created a module to test options described in #20. It builds sort of landing page with blocks that can be slow down.

https://github.com/Chi-teck/sample_catalog

Results are quite interesting though predictable.

When blocks are too slow it doesn't matter which way you are doing parallel processing. The results are always quite good. I've managed to get 12x boost using 12 CPU cores. However, when blocks are relatively fast, the cost of spawning new processes becomes significant. In that case the best results were achieved with "co-pilot" server powered by Road Runner. It demonstrated its usefulness even when building each block takes about 5-10 ms.

Overall, landing pages are not the only use case for this. For instance, I have a project with very heavy API endpoint for a collection of entities. Each item in that collection is personalized and is frequently updated. So caching is not possible. Building items in parallel using one of the above mentioned options can potentially improve API performance a big deal.

🇷🇺Russia Chi

Re 18: There are a couple things that can potentially break that calculation.

1. Database locks
When cache is empty the request will likely trigger lots of cache updates that potentially may case raise conditions. Especially when same cache items need to be updated in different blocks.

2. Building vs rendering
Those terms are often misused. Building means creating a render array while rendering is creating an HTML presentation of the content (string or Markup object). The problem here is that blocks typically relies on lazy builders, pre_render callbacks, theme functions etc. That stuff delegates rendering process to theme layer.
Consider this render array. It costs nothing for CPU to produce such content. The main work will happen later when Drupal is rendering content. And that means that async orchestration have to cover theming as well.

$build['content'] = [
  '#theme' => 'example,
]
🇷🇺Russia Chi

Any way for sites where SQL queries is not a bottleneck the only solution would be having additional PHP processes.

Could think of a few options here:

1. Spawning new processes with proc_open (Symfony Process). For example, for landing pages you described about those could be very simple PHP scripts that do just one thing - render a single block.

2. Rendering blocks with PHP-FPM through hollodotme/fast-cgi-client. That should be faster than CLI workers as it compatible with Opcache and APCu. And supports async requests as well. Also FPM has some options to control number of workers in the pull.

3. Having a few workers connected to a queue. They should listen for jobs (block rendering) and reply instantly. That means Drupal DB based queue is not quite suitable as it would require frequent polling.

4. The most extreme option. Having a multi-thread socket server written in PHP with Drupal bootstrapped. It should be able to handle multiple connections simultaneously. So when a Drupal site needs to render a dozen heavy blocks it just send tasks to that sever to render blocks in parallel.

I suppose nothing of that can be fully implemented in Drupal core. However it could provide some async API to allow delegation of block rendering to external systems.

🇷🇺Russia Chi

Re: #18. We can start rendering blocks with fast queries while waiting for query results from blocks with slow queries? Is it correct?

🇷🇺Russia Chi

Additionally, other CPU intensive tasks (like rendering results) could be happening while waiting for all three queries to come back.

That's unclear. How do we render results without having those result yet?

Drupal is both i/o and CPU bound, but we can do both database query execution and CPU-intensive tasks in parallel once we have async database queries implemented.

Having async DB driver in place we can run queries in parallel. But is it really possible for CPU tasks like rendering?

🇷🇺Russia Chi

@catch Those number are not for measuring site performance. The point was to check how many IO bottlenecks we potentially have. And again checking it in CLI SAPI is not correct.

As of benchmarking, testing just front page may not be sufficient. I think Drupal needs a comprehensive set of load tests that can help to figure out performance gains and track performance regressions.

I created a few K6 scenarios to for testing performance of Drupal site. Wonder if Drupal core could implement something similar as part of CI workflow.
https://github.com/Chi-teck/k6-umami

🇷🇺Russia Chi

Re #13. It was custom project with a few millions entities. However the front page is just a user login form without extra blocks.

Here are results with brand new D11 installation.
Cold cache

real    0m0.638s
user    0m0.346s
sys     0m0.103s

Warned cache

$ time php index.php > /dev/null

real    0m0.123s
user    0m0.086s
sys     0m0.030s

Though standard profile does render many things on front page.

🇷🇺Russia Chi

Yes, there are lots of IO operation

I guess those were mostly file operations performed by Composer autoloader, because in CLI Opcache and Apcu do not make much sense. In real HTTP request served by FPM the difference between real and user will be much smaller.

🇷🇺Russia Chi

That's not clear how will Drupal benefit from this.

This command can give a very rough estimation of how much request time we could potentially save. Compare real and user time.

$ time php index.php > /dev/null

real    0m0.242s
user    0m0.152s
sys     0m0.055s

Yes, there are lots of IO operations but not many of them can be taken out of main code flow because subsequent steps typically depend on previous ones. I believe the earlier mentioned cases (Cache Prewarm and Big Pipe) won't give big performance gain. They need benchmarks to prove their usefulness.

🇷🇺Russia Chi

a separate Twig extension

An example of such extension.
https://github.com/jawira/case-converter-twig

Integration it into Drupal should be as simple as declaring a service in yml file.

🇷🇺Russia Chi

There is a couple reason why this shouldn't be part of Twig Tweak.

1. Twig Tweak does not have to incorporate all possible Twig functions and filters. There is no such a goal to become the only Twig related module in Drupal. Specific use cases should be addressed in specific modules. Twig casings covers quite specific use case. Btw, I did similar filters in Drupal Code Generator.

2. Twig Tweak does not aim to improve Twig itself. It's primary focused on integration with Drupal. Сheck out list of our functions and filters. They are almost all related to Drupal. I think general things like string transformation should be implemented in Twig core or in a separate Twig extension (ordinary Composer package, not a Drupal module).

Thanks.

🇷🇺Russia Chi

I wonder if $entity->toUrl()->access() is more appropriate here than $entity->access('view').

🇷🇺Russia Chi

chi changed the visibility of the branch 3483370-drupalcorecachememorybackend-should-not to hidden.

🇷🇺Russia Chi

Once core is converted, it will be possible for a site to run without any procedural hook implementations at all.

This requires that none of the installed contributed modules implement procedural hooks. Right?

🇷🇺Russia Chi

@nicxvan yes.

Stack-trace looks like follows

(
    [0] => Array
        (
            [file] => /web/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php
            [line] => 332
            [function] => parse
            [class] => Drupal\Component\Annotation\Doctrine\StaticReflectionParser
            [type] => ->
        )
    [1] => Array
        (
            [file] => /web/core/lib/Drupal/Core/Hook/HookCollectorPass.php
            [line] => 164
            [function] => getMethodAttributes
            [class] => Drupal\Component\Annotation\Doctrine\StaticReflectionParser
            [type] => ->
        )
    [2] => Array
        (
            [file] => /web/core/lib/Drupal/Core/Hook/HookCollectorPass.php
            [line] => 122
            [function] => collectModuleHookImplementations
            [class] => Drupal\Core\Hook\HookCollectorPass
            [type] => ->
        )
    [3] => Array
        (
            [file] => /web/core/lib/Drupal/Core/Hook/HookCollectorPass.php
            [line] => 74
            [function] => collectAllHookImplementations
            [class] => Drupal\Core\Hook\HookCollectorPass
            [type] => ::
        )
🇷🇺Russia Chi

This somehow broke tests on a custom module. Cannot reproduce it locally, but CI output is full of such errors.

Notice: file_get_contents(): Read of 12288 bytes failed with errno=21 Is a directory in web/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php on line 184

After some research I found a contributed module that faced same issue.
🐛 phpunit (next minor) failing Active

🇷🇺Russia Chi

Right. It needs update hook.

🇷🇺Russia Chi

probably because that output was not in PHPUnit 9

In PHPUnit 9 that line is printed only when --verbose option is provided.

🇷🇺Russia Chi

I wonder if we really need to update the version manually. Can we fetch the version from Composer instead?

Composer\InstalledVersions::getVersion('drupal/core')
🇷🇺Russia Chi

@socialnicheguru this issue is about storing keys in settings not state.

🇷🇺Russia Chi

Could not reproduce.
This might be somehow related to your site timezone.

🇷🇺Russia Chi

Is there at least one contributed project that ships with its own phpinit.xml file?

🇷🇺Russia Chi

Maybe somehow the phpunit.xml config is not being loaded?

It's certainly not loaded.

🇷🇺Russia Chi

Confirm the date value was updated to tomorrow in the display and the edit form.

Did you check the date value in the database? I wonder if it's only related to formatter.

🇷🇺Russia Chi

I just created one more generator for preload script.
https://www.drupal.org/project/opc

However, while profiling my project I found another way to optimize opcache.
There are a couple opcache settings which default values are not suitable for production.

opcache.validate_timestamps=1
opcache.revalidate_freq=2

A typical Drupal sites loads about 1k - 2k files per request. With above configuration it'll revalidate opcache every 2 seconds.

Turning off opcache.validate_timestamps gave me almost same speed boost as preloading. I think, as long as you don't edit code on production that setting can be disabled. The invalidation should happen through php-fpm reload on deployment.

🇷🇺Russia Chi

For those who might be stucked

The currently used constraint should not block you from using twig/twig v3.14.0

https://semver.madewithlove.com/?package=twig%2Ftwig&constraint=%5E3.10.3

Production build 0.71.5 2024