I'm working with a 3rd party composer library that doesn't currently support PHP 8.2 and trying to add compatibility to my application.
The 3rd party package has the following classes:
class Configuration { }
class ApiClient extends Configuration { }
I then have another extension:
class MyApiClient extends ApiClient { }
I am receiving the following error:
[E_DEPRECATED] Creation of dynamic property MyApiClient::$propertyName is deprecated
The fix is supposed to be adding #[AllowDynamicProperties], however this isn't actually doing anything.
I have the following:
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
And still receive the deprecation error.
I have even edited the underlying library files, to end up with the following:
#[AllowDynamicProperties]
class Configuration { }
#[AllowDynamicProperties]
class ApiClient extends Configuration { }
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
And yet the deprecation error persists.
I'm at a bit of a loss as I can see no reason the attributes wouldn't be taking effect. What am I missing? I'm definitely editing the right files, and if I define the properties inside MyApiClient the deprecation error goes away.
Edit:
As a clearer example as one is seemingly needed:
class Configuration
{
public function __construct()
{
$this->propertyName = 'foo';
}
}
class ApiClient extends Configuration { }
class MyApiClient extends ApiClient { }
Error: [E_DEPRECATED] Creation of dynamic property MyApiClient::$propertyName is deprecated
Expected fix is to add #[AllowDynamicProperties]:
class Configuration
{
public function __construct()
{
$this->propertyName = 'foo';
}
}
class ApiClient extends Configuration { }
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
However, even with this attribute, the deprecation error persists.
Error: [E_DEPRECATED] Creation of dynamic property MyApiClient::$propertyName is deprecated
Even when the underlying composer library files are manually edited (which I cannot do as a long term fix) it does not work:
#[AllowDynamicProperties]
class Configuration
{
public function __construct()
{
$this->propertyName = 'foo';
}
}
#[AllowDynamicProperties]
class ApiClient extends Configuration { }
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
Error: [E_DEPRECATED] Creation of dynamic property MyApiClient::$propertyName is deprecated
The only way to resolve the error is to define the property:
class MyApiClient extends ApiClient
{
public $propertyName;
}
However, this should not be necessary.
The issue is namespaces - I was unaware Attributes were scoped to the current namespace or that namespaces would have any relevance.
So:
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
Needs to be:
#[\AllowDynamicProperties]
class MyApiClient extends ApiClient { }
Alternatively:
use AllowDynamicProperties;
#[AllowDynamicProperties]
class MyApiClient extends ApiClient { }
Without this, usage of the Attribute fails silently.
Related
I have an abstract question for you.
Question:
How can a subclass that extends an abstract class register itself to the abstract class or another class?
Problem:
Guess we have a module master named ModuleMaster and maybe someone else writes another modules to handle a specific problem without modifying the master class and named it ModuleA. For that reason we want to implement a dynamic loading of problem solutions.
My idea:
File: Extensions.php:
namespace Project\Extensions;
class Extensions
{
public function getLoadedModules()
{
var_dump(ModuleMaster::LOADED_MODULES);
}
}
File: Modules\ModuleMaster.php:
namespace Project\Extensions\Modules;
abtract class ModuleMaster
{
public const LOADED_MODULES = array();
}
File: Modules\ModuleA.php:
namespace Project\Extensions\Modules;
class ModuleA extends ModuleMaster
{
}
I hope you understand what I mean and can help with that abstract problem.
This is very strange to use. It's probably a better design to have an external registry for your module. But I think you're asking for this:
File: Extensions.php:
namespace Project\Extensions;
use \Project\Extensions\Modules\ModuleMaster;
class Extensions
{
public function getLoadedModules()
{
var_dump(ModuleMaster::getLoadedModules());
}
}
File: Modules\ModuleMaster.php:
namespace Project\Extensions\Modules;
abstract class ModuleMaster
{
public static function getLoadedModules() {
$parent = self::class;
return array_values(array_filter(\get_declared_classes(), function ($class) use ($parent) {
return in_array($parent, class_parents($class));
}));
}
}
File: Modules\ModuleA.php:
namespace Project\Extensions\Modules;
use \Project\Extensions\Modules\ModuleMaster;
class ModuleA extends ModuleMaster
{
}
Example use:
$e = new \Project\Extensions\Extensions;
$e->getLoadedModules();
Example result:
array(1) {
[0]=>
string(34) "Project\Extensions\Modules\ModuleA"
}
Please note that the code only works if all your class files are included into the context before running getLoadedModules(). PHP won't know your class exists if it is not already loaded into the context.
You seem to be trying to create a capability in the parent class which is not required or inappropriate in the child class. This is the opposite of inheritance and hence an anti-pattern. Further, even though it might be considered as an extension of reflection, you are trying to put runtime data in a class - that's not what classes are for.
You've also not explained in any way that I can understand why you want to do this.
I suspect you really want to implement a factory, strategy or a registry object.
I created class named Task in the App\Entity namespace and my problem is in my another class I want to use it but it doesn't detect my class and give me error:
Attempted to load class "Task" from namespace "App\Entity".
Did you forget a "use" statement for another namespace?
this my Task.php:
namespace App\Entity;
class Task
{
protected $task;
public function getTask()
{
return $this->task;
}
public function setTask($task)
{
$this->task = $task;
}
}
This is my DefaultController that uses the Task class:
use App\Entity\Task;
class DefaultController
{
}
Depending upon your autoloader settings, if you are using composer, you can have it such that autoloading only happens from the pre-built class map - called 'authoritative classmap' (https://getcomposer.org/doc/articles/autoloader-optimization.md).
So, when you add a class to your source, it isn't recognised by the pre-built autoloader. Try running composer dump to let the autoloader load via standard PSR-0/PSR-4 rules.
If you are using the autoloader on PSR-0/PSR-4 mode, then maybe you've not put the file in the right directory.
I encountered this issue using the repository pattern. Currently I use an interface, and a custom class to achieve it, then type-hint it into the controller's construct and because of Laravel, it will solve the repositories' dependencies automatically and recursively.
I also do this in a service provider:
$this->app->bind(path/to/repoInterface,path/to/implementationClass)
However, because of the way I coded these repositories, in order to avoid code duplication, I created an abstract class that has a common method to all these repositories. This class is as follows:
abstract class CommonRepo{
public function __construct(SomeModelClass model){}
public function commonMethod(){//Code here}
And my repositories have the following structure:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI{
public function __construct(){
parent::__construct();
}
}
Laravel doesn't like this, so its giving this error:
Argument 1 passed to path/to/repo/CommonRepo::__construct() must be an instance of path/to/model/SomeModelClass, none given, called in...
So, obviously is not resolving the dependency of the class CommonRepo, but it does resolve the dependencies on the normal repositories.
I'd like, if it's possible, to use type-hinting (the Laravel way) without having to do anything related to the new operator
How can I, then, resolve that class's dependencies ?
PD: Using Laravel 5.2
Parent constructor is called like normal function without touching dependency resolver so you should do one of two possibilities:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(SomeModelClass $model){
parent::__construct($model);
}
}
or
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(){
parent::__construct(App::make(SomeModelClass::class));
}
}
nice question. I did some tinkering, though I don't know if this is what you're looking for. But you can dynamically create an instance of Eloquent model required by your repository class.
Let's say you have your User model class stored in app\Models\User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
You then create a base abstract class for all of your repository classes: app\Repositories\BaseRepository.php. This is where you place all common functionalities for your repository classes. But rather than injecting the Eloquent instance through the constructor, you may add a method named getModel() to dynamically create an instance of Eloquent model for your repository.
<?php
namespace App\Repositories;
use ReflectionClass;
use RuntimeException;
use Illuminate\Support\Str;
abstract class BaseRepository
{
protected $modelNamespace = 'App\\Models\\';
public function getById($id)
{
return $this->getModel()->find($id);
}
public function getModel()
{
$repositoryClassName = (new ReflectionClass($this))->getShortName();
$modelRepositoryClassName = $this->modelNamespace . Str::replaceLast('Repository', '', $repositoryClassName);
if (! class_exists($modelRepositoryClassName)) {
throw new RuntimeException("Class {$modelRepositoryClassName} does not exists.");
}
return new $modelRepositoryClassName;
}
}
Now let's say you want to create a repository for your User model, and this user's repository must implement the following interface: app\Repositories\UserRepositoryInterface.php
<?php
namespace App\Repositories;
interface UserRepositoryInterface
{
public function getByEmail($email);
}
You create app\Repositories\UserRepository.php class and simply extend it from the BaseRepository class. Also don't forget to implement all specific implementations defined on UserRepositoryInterface.
<?php
namespace App\Repositories;
use App\Repositories\BaseRepository;
use App\Repositories\UserRepositoryInterface;
class UserRepository extends BaseRepository implements UserRepositoryInterface
{
public function getByEmail($email)
{
return $this->getModel()->where('email', $email)->firstOrFail();
}
}
This way you can bind the UserRepositoryInterface to it's implementation like so:
$this->app->bind(\App\Repositories\UserRepositoryInterface::class, \App\Repositories\UserRepository::class);
Finally you can freely inject the UserRepositoryInterface to a controller's constructor or methods. You can also resolve it via service container like this:
$userRepository = App::make(App\Repositories\UserRepositoryInterface::class);
$userRepository->getByEmail('john#example.com');
Of course there's a catch to this approach. The repository class should be started with the associated model, so the InvoiceRepository.php is dedicated for Invoice.php model class.
Hope this help!
This might help. You can listen in for when an object resolves and set attributes.
$this->app->resolving(CommonRepo::class, function ($object, $app) {
// Called when container resolves object of any type...
$object->commonObject = app(CommonObject::class);
});
Docs: https://laravel.com/docs/5.4/container#container-events
I have the following scenario:
Controller:
class Collect extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->library('phirehose');
$this->load->library('oauthphirehose');
$this->load->library('ghettoqueuecollector');
}
function index() {
// Start streaming/collecting
$this->ghettoqueuecollector('datos');
...
}
Class:
A)
class Ghettoqueuecollector extends Oauthphirehose {
...
}
B)
abstract class Oauthphirehose extends Phirehose {
...
}
C)
abstract class Phirehose {
...
}
When I try to use the controller gives this error:
"Fatal error: Cannot instantiate abstract class Phirehose"
That is lacking adapt? Codeigniter outside these classes work using require. Can I use them in CI? Thanks
$this->load->library() is made to include, instantiate and configure classes, it's not just an alias for require() - you can't use it in that manner.
If you need to extend an abstract class, you'll have to manually include/require it from the file that extends it, like this:
libraries/Oauthphirehose.php:
require_once __DIR__.'/Phirehose.php';
abstract class Oauthphirehose extends Phirehose {}
libraries/Ghettoqueuecollector.php:
require_once __DIR__.'/Oauthphirehose.php';
class Ghettoqueuecollector extends Oauthphirehose {}
Then you just load the one library that you actually need to instantiate:
$this->load->library('ghettoqueuecollector');
I'm working on a Symfony2 project. For useful technical pratictes, I need to import external libraries. So I did it. But this library creates somes *_Exception class who extend from Exception.
My external library file ends with:
class CloudKey_Exception extends Exception {}
class CloudKey_RPCException extends CloudKey_Exception {public $data = null;}
class CloudKey_ProcessorException extends CloudKey_RPCException {}
class CloudKey_TransportException extends CloudKey_RPCException {}
class CloudKey_SerializerException extends CloudKey_RPCException {}
class CloudKey_AuthenticationErrorException extends CloudKey_RPCException {}
class CloudKey_RateLimitExceededException extends CloudKey_AuthenticationErrorException {}
class CloudKey_InvalidRequestException extends CloudKey_RPCException {}
class CloudKey_InvalidObjectException extends CloudKey_InvalidRequestException {}
class CloudKey_InvalidMethodException extends CloudKey_InvalidRequestException {}
class CloudKey_InvalidParamException extends CloudKey_InvalidRequestException {}
class CloudKey_ApplicationException extends CloudKey_RPCException {}
class CloudKey_NotFoundException extends CloudKey_ApplicationException {}
class CloudKey_ExistsException extends CloudKey_ApplicationException {}
class CloudKey_LimitExceededException extends CloudKey_ApplicationException {}
And when I try to instance my object in controller, Symfony returns this:
Fatal error: Class 'CD\DMBundle\Entity\Exception' not found in /var/www/carpediese/src/CD/DMBundle/Entity/CloudKey.php on line 513
I think Exception class is native PHP5+ class. How can I tell it to Symfony?
Remember to properly set the use statements in files which use the Exception class.
EDIT:
When you refer to any class in PHP 5.3+ just below the namespace declaration you need to add which namespaces you are using for the referenced class (or use the whole namespace when referencing the class). So, if the Exception class you are using belongs to say someLibrary\ClouKey\Exceptions\ namespace you should either have
use someLibrary\CloudKey\Exceptions\Exception;
at the beginning of the file, just below namespace, or use the whole namespace when defining your new class:
class CloudKey_Exception extends someLibrary\CloudKey\Exceptions\Exception {}
EDIT 2:
In the class you are using the Exception is indeed the native PHP class so \Exception should be used. The error you get is generated by this part of the CloudKey class:
public function __get($name)
{
if (!isset($this->objects[$name]))
{
$class = 'CloudKey_' . ucfirst($name);
if (!class_exists($class))
{
$class = 'CloudKey_Api';
}
$this->objects[$name] = new $class($this->user_id, $this->api_key, $this->base_url, $this->cdn_url, $name, $this->proxy, $this->timeout);
$this->objects[$name]->parent = $this;
}
return $this->objects[$name];
}
According to the documentation (http://php.net/manual/en/language.oop5.basic.php):
If a string containing the name of a class is used with new, a new instance of that class will be created. If the class is in a namespace, its fully qualified name must be used when doing this.
So you have to edit the quoted part of the code to use the whole namespace inside the string for the class name.