I am using Laravel and eclipse as my IDE. I am using the laravel-ide-helper package for autocompletion.
I am calling methods from an eloquent model object.
When I type in
User::find
eclipse provided me with:
find($id, $columns) : \Illuminate\Database\Eloquent\Model.
which means the "find" method returns an \Illuminate\Database\Eloquent\Model instance.
However, when I type in
User::where
eclipse provided me with the following:
where($column, $operator, $value, $boolean) : $this
which means the function "where" returns
$this
Now, I don't really know what $this means because as I understand it "where" should return a query builder instance. As far as I know, $this means the object caller of the method (in this context, the User model itself). But it clearly does not return the model. I suspect that I do not understand what $this means in this context.
What am I missing?
The find() and where() methods do not exist on the Model class, so calls to these methods ends up falling through to the PHP magic method __call() which Laravel has defined. Inside this magic method, Laravel forwards the method call to a new query builder object, which does have these methods.
The query builder class' find() method returns a Model, and its where() method returns a reference to itself ($this) so that you can fluently chain more method calls to the builder.
All of this can make it hard for an IDE to provide hints (IntelliSense), which is where packages like laravel-ide-helper come in. They basically generate files full of interfaces that your IDE can use to understand what magic methods and properties exist for various classes, but in some cases these method signatures still fall short of what you might like to know about the code structure.
In this case the IntelliSense suggestions are apparently populating from the docblock for \Illuminate\Database\Eloquent\Builder::where():
/**
* Add a basic where clause to the query.
*
* #param string|array|\Closure $column
* #param mixed $operator
* #param mixed $value
* #param string $boolean
* #return $this
*/
public function where($column, $operator = null, $value = null, $boolean = 'and');
You can see that the return type is defined as $this. At this point, some IDEs may be smart enough to understand the meaning and provide suggestions for an instance of that class. However, this could become more complicated if the method definitions your IDE is parsing are being generated by packages like laravel-ide-helper. In that case it depends not only on the capabilities of your IDE, but also on the output of the helper package.
Eclipse works purely off the method comments in the source code for its hints, so if you look at the source code for Builder which is the returned type of query(), it has for find...
/**
* Find a model by its primary key.
*
* #param mixed $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null
*/
public function find($id, $columns = ['*'])
for where() it is...
/**
* Add a basic where clause to the query.
*
* #param string|\Closure $column
* #param string $operator
* #param mixed $value
* #param string $boolean
* #return $this
*/
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
As it can only add one type hint it uses the first from find() which is \Illuminate\Database\Eloquent\Model and the only option from where() is $this.
Related
I'm working on a Laravel web project that is fully integrated with external/remote Rest API. I'm trying to pass a model object that returned from API to a GET route by implicitly route binding but the default behavior that Laravel did is trying to reference this model object from the database while no database connection defined in my application.
You can customize the resolution logic:
class ApiModel
{
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
return Http::get('api/path/for/model')->json();
}
}
If you need to further modify the model class to have an eloquent-like model, you can use or borrow from jenssegers/model.
I have a project that utilised MVC where the view file is inherting $this which refers to a view class attached to the controller.
Helper classes have been attached in some of the views and are used like follows:
<?=$this->someHelper->renderSomething()?>
I was hoping to help devs and the IDE out by doing this:
/** #var SomeHelper $this->someHelper */
It's not supported, seemingly. Is there a way to achieve this?
I can only find a workaround at the moment, to declare the helper as a new variable and include a #var statement for that.
It's not possible, you are supposed to type hint the $this instead. If $this is not any concrete class you can type hint, create a fake class/interface instead which will act as a helper to the IDE:
// somewhere outside of your code base, but accessible by the IDE
// use the name of your choice
interface CodeIgniterMvc
{
/**
* #return string
*/
function renderSomething(): string;
/**
* #param array $filter Filtering conditions
* #return \Your\App\Models\User[]
*/
function getUsers(array $filter): array;
}
and in the views:
/** #var $this CodeIgniterMvc **/
Of course include in the repository so every team member can gain such benefits.
I like to either type-hint or starting in PHP7 actually show the return value of a getter function. But with One-To-Many relationships in Doctrine / Symfony, I'm still stuck and am not sure what to add to the #var tag.
[...]
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $features;
/**
* What goes into var here?
*
* One Product has Many Features.
* #ORM\OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
public function __construct()
{
$this->features = new ArrayCollection();
$this->name = 'New Product Name';
}
/**
* #return Collection
*/
public function getFeatures(): Collection
{
return $this->features;
}
[...]
Currently I’m using #var Collection and can then use the Collection functions. But what would be the »proper« thing to return? Is it indeed Collection? Or is it ArrayCollection? I’m tempted to use Features[] in order to use the functions of Feature, if I need to (instead of typehinting), but it doesn’t feel right.
What would be the »cleanest« / stable way to do this?
If you want to keep the docblock I would use the union type | to both specify the Collection and the list of values it contains like:
/**
* #var Collection|Feature[]
*/
With this your IDE should both find the methods from Collection as well as the Feature-type hints when you get a single object from the collection, e.g. in a foreach.
As to the question of ArrayCollection vs. Collection, it is usually recommended to type hint for the interface (Collection in this case). ArrayCollection offers a few more methods, but unless you really need them I would not bother with the type hint just to get them.
What I tend to do in projects is keep the Collection inside the entity and only pass out an array in the getter like this:
public function getFeatures(): array
{
return $this->features->toArray();
}
public function setFeatures(array $features): void
{
$this->features = new ArrayCollection($features);
}
Be careful, the void return type is not supported in PHP 7.0 yet. The benefit of returning an array is that in your code you don't have to worry about what kind of Collection Doctrine uses. That class is mainly used to maintain reference between objects inside Doctrine's Unit Of Work, so it should not really be part of your concern.
Suppose I have the following PHP function:
/**
* #param string $className
* #param array $parameters
* #return mixed
*/
function getFirstObject($className, $parameters) {
// This uses a Doctrine DQl builder, but it could easily replaced
// by something else. The point is, that this function can return
// instances of many different classes, that do not necessarily
// have common signatures.
$builder = createQueryBuilder()
->select('obj')
->from($className, 'obj');
addParamClausesToBuilder($builder, $parameters, 'obj');
$objects = $builder
->getQuery()
->getResult();
return empty($objects) ? null : array_pop($objects);
}
Basically, the function always returns either an instance of the class specified with the $className parameter or null, if something went wrong. The only catch is, that I do not know the full list of classes this function can return. (at compile time)
Is it possible to get type hinting for the return type of this kind of function?
In Java, I would simply use generics to imply the return type:
static <T> T getOneObject(Class<? extends T> clazz, ParameterStorage parameters) {
...
}
I am aware of the manual type hinting, like
/** #var Foo $foo */
$foo = getOneObject('Foo', $params);
but I would like to have a solution that does not require this boilerplate line.
To elaborate: I am trying to write a wrapper around Doctrine, so that I can easily get the model entities that I want, while encapsulating all the specific usage of the ORM system. I am using PhpStorm.
** edited function to reflect my intended usage. I originally wanted to keep it clean of any specific use case to not bloat the question. Also note, that the actual wrapper is more complex, since I also incorporate model-specific implicit object relations and joins ect.
I use phpdoc #method for this purpose. For example, I create AbstractRepository class which is extend by other Repository classes. Suppose we have AbstractRepository::process(array $results) method whose return type changes according to the class that extends it.
So in sub class:
/**
* #method Car[] process(array $results)
*/
class CarRepo extends AbstractRepository {
//implementation of process() is in the parent class
}
Update 1:
You could also use phpstan/phpstan library. Which is used for static code analyses and you can use it to define generic return types:
/**
* #template T
* #param class-string<T> $className
* #param int $id
* #return T|null
*/
function findEntity(string $className, int $id)
{
// ...
}
This can now be achieved with the IntellJ (IDEA/phpStorm/webStorm) plugin DynamicReturnTypePlugin:
https://github.com/pbyrne84/DynamicReturnTypePlugin
If you use PHPStorm or VSCode (with the extension PHP Intelephense by Ben Mewburn) there is an implementation named metadata where you could specify your own type-hinting based on your code doing the magic inside. So the following should work (as it did on VSCode 1.71.2)
<?php
namespace PHPSTORM_META {
override(\getFirstObject(0), map(['' => '$0']));
}
Yii offers a convenient way to massive assignment to a model. I search on google and can't find any information about massive assignment with Laravel.
So, does Laravel offer the same functionality?
Yes: http://laravel.com/docs/eloquent#mass-assignment
But make absolutely sure you white-list your attributes or you'll open yourself up to serious security issues.
I haven't tried laravel yet, but a quick look on github tells me there's a function:
/**
* Hydrate the model with an array of attributes.
*
* #param array $attributes
* #param bool $raw
* #return Model
*/
public function fill(array $attributes, $raw = false)
I guess you can do something like
$product->fill($_POST['Product']);
as you would do $product->attributes = $_POST['Product'] in Yii