Retrieve words from dictionary when they meet letter requirements
How about removing the dictionary preparation each time at a cost of increasing your dictionary width?
You could have an alphabetized lookup column (the rows aren't alphabetized -- each letter of each word is sorted alphabetically) and a word column:
lookup  |  word
-----------------
aelpp   |  apple
aelmps  |  sample
aip     |  api
aip     |  pia
икотҷ   |  тоҷик

Using your lowercase, alphabetized $needle, when you want to find &quot;whole&quot; matches, you merely search the lookup column with the = operator.
SELECT `word` FROM `dictionary` WHERE `lookup` = 'икотҷ'

When you want to match the $needle characters at a minimum, you call:
SELECT `word` FROM `dictionary` WHERE `lookup` REGEXP '.*и.*к.*о.*т.*ҷ.*'

Leveraging something like this technique: Custom REGEXP Function to be used in a SQLITE SELECT Statement with this intended usage: ~.*и.*к.*о.*т.*ҷ.*~u
This, of course, is just a theoretical suggestion -- I haven't tried to do anything like this before.
And definitely remember to sanitize and escape the $needle to be offered to the query for security reasons.
Mostly I am suggesting that you sacrifice memory for speed.  Only the $needle should be modified with character sorting and strtolower actions.  These processes are expected to be &quot;already done&quot; on words prior to being stored in the dictionary.
Here is another post of mine with the same basic logic: How to best compare these two strings for values even though they are in random order?

If altering the dictionary table structure is unattractive, this is how I would recommend searching for exact character matches in any order:
Code:
function getWordsContainingTheExactSpecifedLetters_inanyorder_nomore_noless(array $dictionary, string $letters, string $encoding = 'UTF-8'){
    $lettersLength = mb_strlen($letters, $encoding);  // call just once and cache
    $lettersSplit = preg_split('//u', mb_strtolower($letters, $encoding), null, PREG_SPLIT_NO_EMPTY);
    sort($lettersSplit);

    $result = [];
    foreach ($dictionary as $word) {
      if(mb_strlen($word, $encoding) == $lettersLength) {
        $wordSplit = preg_split('//u', mb_strtolower($word, $encoding), null, PREG_SPLIT_NO_EMPTY);
        sort($wordSplit);
        if ($wordSplit === $lettersSplit) {
          $result[] = $word;
        }
      }
    }
    return $result;
}

Of course, you will need to change the qualifying condition if you wish to retain larger words that merely contain the letters.