How can I have phpstan find my custom extension class? - php

To implement checks for some magic _get() functions, I'm trying to implement a "class reflection extension". The manual mentions how to write the extension files themselves, and to install them by adding a service part to the neon file.
I've written an extension, and added it like so:
services:
-
class: TestClass
tags:
- phpstan.broker.methodsClassReflectionExtension
This results in the following error:
Service '0226': Class TestClass not found.
Things I've tried:
using the standalone phar
using the composer install of phpstan
using a specific namespace (PHPStan) for the extension (in the file and in the neon)
using a "random" namespace (in the file and in the neon)
Adding the file to a directory I know is being scanned for analysis
Adding the directory with the extension to the "scanDirectories" parameter
Adding the file to the "scanFiles" parameter
Adding the directory with the extension to the "paths" parameter
Adding a (deprecated) autoload directive and adding the file there.
Several of these 'adding' tries actually warn if the file is not found: if, for instance, I make a deliberate typo in one of these, lets say the autoload one, it will warn me of a missing file. But only if I don't have the service defined.
If I have the service defined and the deliberate typo in there, it returns above class-not-found (and not the typo), so I feel it's checking the extension before any of the parameters?
There seems to be a need for adding my file to a different autoloading mechanism. The question is, which and how?
I'm using
phpstan 0.12.40
php 7.2.33

The extension class needs to be loaded in runtime. You can achieve that in two ways:
Make the class available for the Composer autoloader. It's usually sufficient to configure autoload-dev section of your composer.json.
Create a file called something like phpstan-autoload.php in which you require_once the file with the extension class. And pass the path to the phpstan-autoload.php file as --autoload-file|-a option on the command line when running PHPStan.

Related

Are PHP Namespaces dependent on path of file?

In YII2 Framework
Path of File is root\vendor\yiisoft\yii2-httpclient\Client.php
Namespace defined in above mentioned file is - namespace yii\httpclient;
Now when I use this namespace in other file while setting up Google ReCaptcha
by writing "use yii\httpclient\Client"
then I am getting error "Class yii\httpclient\Client" not found
So I want to know whether namespaces are path dependent ? or is there a routing file or htaccess..etc where I can define the actual path of namespaces used in project, which YII2 compiler will refer to locate the file / class ?
Namespaces themselves are not dependent on file path.
But you are probably mistaking what use clause does.
If you have this line in file:
use yii\httpclient\Client;
It doesn't mean that the class is loaded. It only tells parser that you mean yii\httpclient\Client every time you use Client class in that file.
PHP has something called autoload to make sure you don't have manually require files for each class you are using. Autoloaders are called every time you are using some class if that class hasn't been loaded yet. When they are called they are given the class name and they check if they know how to load that class.
Now, even if the namespaces itself are not dependent on file path autoloaders usually uses those namespaces to decide where to look for the file containing that class.
And as Nigel Ren mentioned in comment, there exist PSR-4 recommendation how to choose namespace and file structure to make sure that autoloader will know where to look for class.
Yii2 projects usually uses 2 autoloaders.
The Yii's own autoloader and autoloader generated by composer.
Since your question is about class that comes from vendor\yiisoft\yii2-httpclient the autoloader generated by composer.
If you check the composer.json file in that package you can see that it has autoloader section with psr-4 key. That tells composer that when it generates its autoloader it should be set to look for any class from yii\httpclient namespace in src folder of that package.
To make sure the composer's autoloader is working properly you have to go through following steps:
The yiisoft\yii2-httpclient package should be installed by composer.
If you need to regenerate composer's autoloader you can run:
composer dump-autoload
The composer autoloader must be included in your application's entry point (usually /web/index.php or /yii files).
Check if those files have this line:
// in case of /web/index.php
require(__DIR__ . '/../vendor/autoload.php');
//in case of /yii
require(__DIR__ . '/vendor/autoload.php');

PhpStorm - Cannot find declaration to go to

I try to lookup the declaration of File but PhpStorm says Cannot find declaration to go to.
I also tried it with netbeans, it can't find the declartion too.
I also tried to lookup the alias use File;
I get No usage found in project files.
How does my code even know what it has to do if It can't find any declarations? This makes no sense to me.
How can I find out where File is declared?
How does my code even know what it has to do if It can't find any declarations?
By using an autoloader. This is basically a function which is called whenever an unknown class is referenced, and attempts to define it, usually by including a file according to some naming convention. You will need to find how your particular framework manages this.
Note that it's possible it's including a file from outside the directory you have set up as the "project" in your IDE. Once you've figured out where it is, you may be able to configure your IDE to know about these extra files.
How can I find out where File is declared?
Find a place where the class is used, and using a debugger or just "dump value and die", you can use ReflectionClass::getFilename() to find out about it:
$r = new \ReflectionClass(File::class);
$r->getFilename();
Note that the File::class syntax (available since PHP 5.5) gives you the fully qualified name of the class, ignoring any aliasing or namespace imports.
It's also possible for an extension (a library written in C, not PHP) to define a class. In that case, ReflectionClass::getFilename() will return false, and you'll need to use ReflectionClass::getExtensionName(), then track down the documentation for that extension.
Laravel is quite "opinionated" in the way they use facades.
Apart from the PHPStorm gudelines how to deal with it, I find artisan tinker a simplest IDE-independent way to get familiar with new codebase.
The command provides a REPL shell, so if you are curious of where the File is actually defined, just invoke it, to get some information from the error message:
>>> File::delete()
PHP warning: Missing argument 1 for Illuminate\Filesystem\Filesystem::delete(), called in /path/to/project/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php on line 213 and defined in /path/to/project/app/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php on line 118
PHPStrom scans all files in Project Root folder. Add an external library (framework) you use to Project Root folder. Maybe you should instal dependecies via composer.

Difficulty instantiating classes in psysh

I am having difficulty getting psysh to instantiate classes.
I am trying to use PSR-4 namespaces, and have registered a psr-4 autoload in composer like this:
"autoload": {
"psr-4": {
"System\\": "phpclasses/"
}
},
There is a class in phpclasses\Test.php, class name Test with a static method called hello().
I open a command shell, start psysh, and psysh appears to be working normally.
If I try to run Test::hello(); it will fail, unless I call it like this first: echo System\Test::hello();
This actually fails with the message:
PHP Fatal error: Class 'System\Test' not found in eval()'d code on line 1
but then I can successfully run: echo Test::hello();
echo System\Test::hello(); will never work
I tried Use System; and use System\Test; has no beneficial effect.
Every class I use, I have to go through this routine, which is kind of a drag because some of the classes uses static methods, and each of those will only work if each class has gone through that fail first routine.
Basically the same technique must be used for static or non-static methods.
I am running psysh in a command shell in windows 10, xampp (php 5.6), composer (current) installed.
Any suggestions for what I am doing wrong or need to do differently?
The trouble here is that you're not following PSR-4. With the config you provided, it's expecting to find classes in the System namespace inside your phpclasses folder. So, for example, the file Test.php would have the class System\Test.
To just fix it, either change the prefix in your autoload settings to "", or add namespace System; to your Test.php file. If you want to understand why it's acting like it is, you have to understand a bit about how autoloading works in PHP:
PHP lets you register an autoloader to find classes which haven't been encountered yet. The autoloader is handed a class name, and given a chance to find it. Usually they work by mapping class names to files in some way. When they're asked for an unknown class, they translate the class name to a file name, and try to require the file.
PSR-4 is a standard for setting up such an autoloader, and Composer comes with a PSR-4 compliant autoloader for free. For it to work right, you have to lay out your classes and namespaces like PSR-4 expects. If you don't, you can run into strange issues like you're encountering.
When you first tried calling Test::hello(), the class wasn't defined. Your PSR-4 autoloader translated that to a file name, but per your config, there's nowhere defined for non-namespaced classes to live, so it couldn't find a file to load, and it ended up loading nothing. After the autoloader had a chance, PHP still didn't know about that class, so it threw an error.
When you tried calling System\Test::hello(), your PSR-4 autoloader looked it up in the config and translated it to a filename (phpclasses/Test.php), which did exist this time, so it loaded that file. PHP then tried calling the method, but it didn't know about that class, so it threw an error.
The third time, it had already loaded your file and discovered the non-namespaced Test class. So when you tried calling it again, it didn't even bother with the autoloader, and just executed your method.

Would a class loaded with an extension override a plain PHP Composer fallback?

Let's say we have a class implemented in a PHP extension called My\Example\Object. To allow for graceful degredation, I would like to provide a standard PHP implementation for the same class, also as My\Example\Object. If the extension is loaded, would it use the extension one instead of the PHP class? Assume that we're using the Composer autoloader if that makes any difference.
So, with the extension loaded, would the following use the .php file at all?
<?php
use My\Example\Object;
new Object();
Yes, it does.
The namespace from the extension will be resolved first.

PHP Zend library results in Fatal Error: class 'Zend\[whatever]' not found

I'm hoping someone can spot what I've forgotten to do. Here are my steps:
Downloaded and unpacked the ZendFramework-2.3.5 into /usr/share.
Updated include_path in my php.ini file to include '/usr/share/ZendFramework-2.3.5/library' per the INSTALL.md, and restarted Apache to confirm the path is set (now ".:/usr/share/php:/usr/share/ZendFramework-2.3.5/library").
Created a test script in my web document root (using the class 'CamelCaseToUnderscore' as an example):
use Zend\Filter\Word\CamelCaseToUnderscore;
$filter = new CamelCaseToUnderscore();
echo $filter->filter('BigsAndLittles');
...and I get the fatal error "class 'zend\filter\word\camelcasetoseparator' not found".
In order to do use Zend classes like this, do I need to do some additional configuration or create an autoloader or something to find them? Seems like this should have worked. If I include the CamelCaseToUnderscore.php file in a require_once statement, then I get a fatal error that it's parent class doesn't exist (CamelCaseToSeparator.php). What am I missing?
You can use require 'Zend/Mvc/Application.php' to test if your include path is correct, but you will need an autoloader:
http://framework.zend.com/manual/current/en/modules/zend.loader.standard-autoloader.html.
You can find an example here (lines 18-20):
https://github.com/zendframework/zf2/blob/master/demos/Zend/Feeds/consume-feed.php
I strongly suggest using composer as it will save you a lot of time troubleshooting your include paths, but it also allows you manage version better. It makes it easier for other developers and to deploy your code.
Starting with composer is very easy, just install it and create composer.json:
https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
Run:
composer require zendframework/zendframework
Composer will download all libraries to vendor folder and will generate an autoloader, all you have to do is to include
require 'vendor/autoload.php';
https://getcomposer.org/doc/01-basic-usage.md#autoloading
Most popular PHP frameworks use composer for managing dependencies:
https://github.com/zendframework/zf2/blob/master/composer.json
https://github.com/symfony/symfony/blob/2.7/composer.json

Categories