well, i'm wondering how they use the application class as an array
for example in application file
vendor/laravel/framework/src/illuminate/Container/Application.php
in magic methods __set and __get they use $this as an array
here is the code
/**
* Dynamically access container services.
*
* #param string $key
* #return mixed
*/
public function __get($key)
{
return $this[$key];
}
/**
* Dynamically set container services.
*
* #param string $key
* #param mixed $value
* #return void
*/
public function __set($key, $value)
{
$this[$key] = $value;
}
but i don't understand how this works without thorwing any errors
i have tried something like that but it give me the following error
Here is My Code
class Container{
public function __get($key){
return $this[$key];
}
public function __set($key,$val){
$this[$key] = $val;
}
}
$app = new Container();
$app->test = 'ok';
echo $app->test;
Fatal error: Cannot use object of type Container as array in C:\xampp\htdocs\test\test.php on line 10
Any explaination for this please ?
by the way i'm using laravel v4.2.12
Laravel's Illuminate\Container\Container class implements PHP's ArrayAccess interface, which provides the array access syntax to a class. To implement this interface, you must provide an implementation for the offsetExists(), offsetGet(), offsetSet(), and offsetUnset() methods. Once your class correctly implements the ArrayAccess interface, you can use the array access syntax.
This is just one example of an ArrayAccess implementation, but you can try something like this:
// note the implementation
class Container implements ArrayAccess {
protected $data = array();
public function offsetExists($offset) {
return array_key_exists($offset, $this->data);
}
public function offsetGet($offset) {
return $this->data[$offset];
}
public function offsetSet($offset, $value) {
$this->data[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->data[$offset]);
}
public function __get($key) {
return $this[$key];
}
public function __set($key, $val) {
$this[$key] = $val;
}
}
$app = new Container();
$app->test = 'ok';
echo $app->test.PHP_EOL.PHP_EOL.print_r($app, true);
You can also look at the Illuminate\Container\Container implementation to see how Laravel actually does it.
Related
ENUM types are awesome. They allow strict value restrictions and make code refactoring easy. Unfortunately, PHP not only lacks these until version 8.1, the Doctrine DBAL also lacks behind and does not offer a easy to use solution out of the box. I was looking for a solution that would allow me:
native ENUM type in DB
no magic strings in PHP
as little code repetition as possible
PHP 7.4+ (cannot use PHP 8.1)
This question is to be self-answered for those looking for such solution, because after hours of struggle, I am quite proud of what I made. See below, hope it helps:
Start by creating an abstract base class which extends Doctrine\DBAL\Types\Type. This allows it to be used as a type in Entity column declarations.
<?php
namespace App\DBAL;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Exception;
use InvalidArgumentException;
use ReflectionClass;
abstract class EnumType extends Type
{
private static ?array $constCacheArray = NULL;
public static function getConstants() : array
{
if (self::$constCacheArray == NULL)
self::$constCacheArray = [];
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
public static function isValidName($name, $strict = false) : bool
{
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}
public static function isValidValue($value, $strict = true) : bool
{
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}
protected static string $name;
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
$values = array_map(function ($val) {
return "'" . $val . "'";
}, self::getConstants());
return "ENUM(" . implode(", ", $values) . ")";
}
/**
* #param $value
* #param AbstractPlatform $platform
* #return mixed
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $value;
}
/**
* #param $value
* #param AbstractPlatform $platform
* #return mixed
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
$this->checkValue($value);
return $value;
}
/**
* #param $value
* #throws InvalidArgumentException
*/
public function checkValue($value): void
{
if (!self::isValidValue($value)) {
throw new InvalidArgumentException("Invalid '" . static::$name . "' value.");
}
}
public function getName(): string
{
return static::$name;
}
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
public static function getValuesArray(): array
{
return self::getConstants();
}
/**
* #throws Exception
*/
public static function getChoicesArray(): array
{
throw new Exception("Not implemented");
}
}
Credit for the base of this goes to #Brian Cline
Whats important is that this class provides some helper functions with Reflection, but it also has inherited functions that allow it to be used as actual DB type. I will show you the usage with an example below.
This is how you define a new ENUM type:
<?php
namespace App\DBAL;
class AdminRoleType extends EnumType
{
public const ADMIN = 'ROLE_ADMIN';
public const SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
public const CSR = 'ROLE_CSR';
public const MANAGER = 'ROLE_MANAGER';
public const ACCOUNTING = 'ROLE_ACCOUNTING';
protected static string $name = 'admin_role';
}
Pretty simple, right? This out of the box allows you to some cool things in PHP such as:
$myRole = AdminRoleType::CSR; // 'ROLE_CSR'
$isValidRole = AdminRoleType::isValidValue('ROLE_ADMIN'); // true
$isValidRole = AdminRoleType::isValidName('ADMIN'); // true
But still we did not achieve actual ENUM type in our DB table. To do this, first add the following to your config/packages/doctrine.yaml:
doctrine:
dbal:
mapping_types:
enum: string
types:
admin_role: App\DBAL\AdminRoleType
This maps DB ENUM type to local string type (sorry, native ENUMs are not in this solution, but for PHP 8.1 could(?) be possible.)
The last step is your Entity class:
/**
* #ORM\Column(name="admin_role", type="admin_role")
*/
private string $admin_role = AdminRoleType::CSR;
public function getAdminRole(): string
{
return $this->admin_role;
}
/**
* #param string $admin_role
* #return $this
* #throws InvalidArgumentException
*/
public function setAdminRole(string $admin_role): self
{
if(!AdminRoleType::isValidValue($admin_role))
throw new InvalidArgumentException('Invalid Admin Role');
$this->admin_role = $admin_role;
return $this;
}
As you can see the code will throw an exception if you try to set some string that is not allowed value for your ENUM.
And when you do migration, the output should look like:
ALTER TABLE admin CHANGE admin_role admin_role ENUM('ROLE_ADMIN', 'ROLE_SUPER_ADMIN', 'ROLE_CSR', 'ROLE_MANAGER', 'ROLE_ACCOUNTING') NOT NULL COMMENT '(DC2Type:admin_role)'
That's it. When you work in PHP, remember to use AdminRoleType:: class instead of magic strings. If you need to add/remove item in enum, just add/remove public const from the enum class.
I wrote a dependency provider (basically IOC container) for my application. I added the feature of autowiring, although it hits a snag when the class it is trying to autowire doesn't have a constructor.
Uncaught Error: Call to a member function getParameters() on null in C:\xampp\htdocs\src\app\Providers\DependencyProvider.php:68
I am fairly certain it is trying to resolve constructor arguments in the resolveArguments method, on a class that doesn't have a constructor, and this is why the issue is happening.
So, resolveArguments should only be called if the class needs their arguments resolved (autowired), and that is usually only when it has a constructor.
The error message above, is happening because getConstructor is returning null. I am asking what is the best practice to check if a reflection class has a constructor that needs autowiring?
Full class:
<?php
namespace App\Providers;
class DependencyProvider {
private static $objects = [];
/**
* Register an instantiated object to the container.
*
* #param object $object
*/
public static function register(object $object) : void {
self::$objects[get_class($object)] = $object;
}
/**
* Fetch a cached object from the container.
*
* #param string $objectName
* #return object
*/
public static function fetch(string $objectName) : object {
if (array_key_exists($objectName, self::$objects)) {
return self::$objects[$objectName];
}
$object = self::make($objectName);
self::$objects[$objectName] = $object;
return $object;
}
/**
* Creates an object from its name and auto-wires constructor arguments.
*
* #param string $objectName
* #return object
* #throws \ReflectionException
*/
private static function make(string $objectName) : object {
$reflection = new \ReflectionClass($objectName);
if (!$reflection->isInstantiable()) {
throw new RuntimeException($reflection->getName() . ' can\'t be instantiated.');
}
$arguments = self::resolveArguments($reflection);
if (count($arguments) < 1) {
return $reflection->newInstance();
}
else {
return $reflection->newInstanceArgs($arguments);
}
}
/**
* Creates an array of arguments from a reflection class.
* Uses default value if there is one, auto-wires the object if not.
*
* #param $reflection
* #return array
*/
private static function resolveArguments($reflection) : array {
$constructor = $reflection->getConstructor();
$parameters = $constructor->getParameters();
if (!$parameters) {
return $reflection->newInstance();
}
$arguments = [];
foreach ($parameters as $parameter) {
if ($parameter->isDefaultValueAvailable()) {
$arguments[] = $parameter->getDefaultValue();
continue;
}
if ($parameter->getClass() == null) {
exit($parameter->name . ' on ' . $reflection->getName() . ' needs a default value');
}
$arguments[] = self::fetch($parameter->getClass()->getName());
}
return $arguments;
}
}
Edit (misread the question):
Just check that the constructor actually exists before calling that method:
if (! is_null($reflection->getConstructor())) { ... }
I'm trying to use dynamic accessor for Laravel model's virtual attributes. Actually I want to handle the situation that if a property doesn't directly exist / doesn't exist in database, load it's value from config file.
I managed to handle it by writing a accessor for each single attribute, but I find it redundant and ugly. I'm sure it can be done more effectively.
class MyModel extends Model
{
public function getTitleAttribute()
{
return $this->loadAttributeFromConfig('title');
}
public function getSubtitleAttribute()
{
return $this->loadAttributeFromConfig('subtitle');
}
public function getTagAttribute()
{
return $this->loadAttributeFromConfig('tag');
}
public function getIconCssClassAttribute()
{
return $this->loadAttributeFromConfig('iconCssClass');
}
public function getBoxCssClassAttribute()
{
return $this->loadAttributeFromConfig('boxCssClass');
}
public function getBulletsAttribute()
{
return $this->loadAttributeFromConfig('bullets');
}
protected function loadAttributeFromConfig($attribute)
{
return config('myConfigAttributes.' . $this->name . '.' . $attribute);
}
}
$myModel->append(['title', 'subtitle', 'tag', 'iconCssClass', 'boxCssClass', 'bullets']);
My solution works but I consider it ugly.
This can actually be achieved rather easily using the __get magic method. You can override it on a base model class that you inherit or create a trait like so:
trait ConfigAttributes
{
/**
* #param string $key
*
* #return mixed
* #throws \Exception
*/
public function __get($key)
{
// Make sure the required configuration property is defined on the parent class
if (!property_exists($this, 'configAttributes')) {
throw new Exception('The $configAttributes property should be defined on your model class');
}
if (in_array($key, $this->configAttributes)) {
return $key;
}
return parent::__get($key);
}
/**
* #return array
*/
public function toArray()
{
// We need to override this method because we need to manually append the
// attributes when serializing, since we're not using Eloquent's accessors
$data = collect($this->configAttributes)->flip()->map(function ($v, $key) {
return $this->loadAttributeFromConfig($key);
});
return array_merge(parent::toArray(), $data->toArray());
}
/**
* #param string $attribute
*
* #return mixed
*/
protected function loadAttributeFromConfig($attribute)
{
return config('myConfigAttributes.' . $this->name . '.' . $attribute);
}
}
Then in you model class just import the trait and specify your custom fields:
class MyModel extends Model
{
use ConfigAttributes;
protected $configAttributes = [
'title',
'subtitle',
'tag',
'iconCssClass',
'boxCssClass',
'bullets'
];
}
Word of caution: be careful when overriding magic methods on classes defined by Laravel, because Laravel makes heavy use of them and if you're not careful you risk breaking other functionality.
I've been doing a lot of reading on constructors and initialising variables, and I've came across a problem which I'm trying to solve. I'm trying to solve the lack of generics support by introducing a variable that needs to be initialised by the subclass.
<?php
abstract class Collection_Base {
protected $typeOf; // Must be initialised by the subclass
protected $collection = array();
}
class Cookie_Collection extends Collection_Base {
protected $typeOf = 'System\Web\Http_Cookie';
public function set ($item) {
if (!$item instanceof $this->typeOf) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
}
?>
So I was wondering, is it bad practice to include variable which must be initialised by subclass constructor in PHP? Is there anything I need to be aware of when doing so?
While not directly related, I've used the following sources to gather my information:
http://docs.hhvm.com/manual/en/hack.otherrulesandfeatures.classinitialization.php
http://ralphschindler.com/2012/03/09/php-constructor-best-practices-and-the-prototype-pattern
SOLUTION
<?php
abstract class Collection_Base {
protected $collection = array();
public abstract function getType();
private function getTypeInternal () {
$type = $this->getType();
if (is_class($type)) {
throw new \UnexpectedValueException;
}
return $type;
}
public function set ($item) {
$type = $this->getTypeInternal();
if (!$item instanceof $type) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
}
class Cookie_Collection extends Collection_Base {
protected $type = 'System\Web\Http_Cookie';
public function getType () {
return $this->type;
}
}
?>
I thought I recognized this as an anti-pattern, so I looked for where I read about it, but then I remembered it was this: http://en.wikipedia.org/wiki/Call_super, which isn't quite the same thing.
On to what you are doing however. There are a lot of similar libraries which use a practice like this, however they differ by enforcing the practice in way of abstract methods:
abstract class Collection_Base {
protected $typeOf;
protected $collection = array();
/**
* #return string
*/
public function getType()
{
if (null === $this->typeOf) {
$this->typeOf = $this->doGetType();
}
return $this->typeOf;
}
/**
* #return string
*/
abstract protected function doGetType();
}
class Cookie_Collection extends Collection_Base {
/**
* #param $item
*/
public function set ($item) {
if (!$item instanceof $this->getType()) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
/**
* #inheritdoc
*/
protected function doGetType()
{
return 'System\Configuration\Configuration_Element';
}
}
Use a Factory pattern to hide constructor details ... see http://www.phptherightway.com/pages/Design-Patterns.html
Make Collection_Base an abstract class and define a method that returns appropriate class name:
abstract class Collection_Base
{
protected $collection = [];
public function add($item)
{
if (!$item instanceof $this->getType()) {
throw new \InvalidArgumentException();
}
$this->collection[] = $item;
}
abstract protected function getType();
}
class Collection_Cookie extends Collection_Base
{
protected function getType()
{
return Configuration_Element::class;
}
}
Using this approach it's not possible for other developers to forget about type "property".
EDIT:
Using a factory, as suggested by Luca Rocchi is also a very good idea.
I have an object created within a method, and I would like to further process it in another method of the same class.
I tried it like this, but $obj is not passed through to method two():
class SomeClass {
public static function one($id)
{
$obj = new self();
$obj->id = $id;
//...
return $obj;
}
public function two()
{
$obj->id = 2;
return $obj;
}
}
$obj = SomeClass::one($id)->two();
Is there a way to achieve this?
I found this thread How to chain method on a newly created object?, but it's still not quite clear whether this is possible or not.
two is a static method. You're trying to invoke it on an instance, and static methods by definition don't work on a specific instance.
Make the method not static, and manipulate $this instead of $obj.
If I'm not misunderstanding what you're trying to do, if you don't make the second method static, you can return an object and it'll be passed in as $this in the chained call;
class SomeClass {
public $id = 0;
public static function one($id)
{
$obj = new self(); // Create and return new object
$obj->id = $id;
return $obj;
}
public function two()
{
$this->id = 3; // The new object is $this here
return $this;
}
}
$obj = SomeClass::one(5)->two();
Method chaining only makes sense when you are using setters.
In combination with PHPDoc most IDE's (like NetBeans) will support code completion/autocomplete feature again.
p.s note that method chaining can give your more chance on errors like PHP Fatal error: Call to a member function .... on a non-object in .... when mixing in more classes with returning
class Example {
protected $var = null;
public function __construct() {
}
public function __destruct() {
unset($this->var); // explicit memory clearing
}
/**
* #return \Example
*/
public static function create() {
return new self();
}
/**
* #return \Example
*/
public static function getInstance() {
return self::create();
}
/**
* #return \Example
*/
public static function newInstance() {
return self::create();
}
/**
*
* #param mixed $var
* #return \Example
*/
public function setThing1($var) {
$this->var = $var;
return $this;
}
/**
*
* #param mixed $var
* #return \Example
*/
public function setThing2($var) {
$this->var = $var;
return $this;
}
}
Example::create()->setThing1(1)->setThing2('2');