CakePHP with PHPStan: Property does not accept Cake\ORM\Table - cakephp

Working my way through the levels of PHPStan with a new applicaton, I got to level 3 and started getting error messages from all my test fixtures for models. The basic format is as follows:
------
Line tests/TestCase/Model/Table/UsersTableTest.php
------
43 Property Visualize\Test\TestCase\Model\Table\UsersTableTest::$Users (Visualize\Model\Table\UsersTable) does not accept Cake\ORM\Table.
------
The code that this error refers to is:
/**
* setUp method
*
* #return void
*/
public function setUp(): void
{
parent::setUp();
$config = $this->getTableLocator()->exists('Users') ? [] : ['className' => UsersTable::class];
$this->Users = $this->getTableLocator()->get('Users', $config);
}
This setup code was build using cake bake, so I'm not sure what it's looking for. Does anyone else know what will resolve this issue for me?
EDITED: I did a bit of further searching. The only version of the getTableLocator() function I could find associated with this stack was in the TableRegistry class. That class in turn has a function called get() and that function does indeed return an object of type \Cake\Orm\Table:
/**
* Get a table instance from the registry.
*
* See options specification in {#link TableLocator::get()}.
*
* #param string $alias The alias name you want to get.
* #param array $options The options you want to build the table with.
* #return \Cake\ORM\Table
* #deprecated 3.6.0 Use {#link \Cake\ORM\Locator\TableLocator::get()} instead. Will be removed in 5.0.
*/
public static function get(string $alias, array $options = []): Table
{
return static::getTableLocator()->get($alias, $options);
}
So does this mean my tests ought to expect the \Cake\ORM\Table class? TBH, I've yet to do much of anything in the way of testing Models (as you might have guessed), thus I'm not sure the consequences of doing that.

The question is how to deduce from $this->getTableLocator()->get('Users', $config); that it should be returning Visualize\Model\Table\UsersTable.
You can write a dynamic return type extension if you come up with logic that can deduce that from the abstract syntax tree and maybe other places like configuration.
It's possible that the extension https://github.com/CakeDC/cakephp-phpstan might already tackle that, this class definitely looks like that: https://github.com/CakeDC/cakephp-phpstan/blob/master/src/Type/TableLocatorDynamicReturnTypeExtension.php

Related

Symfony: Resolve Doctrine EntityNotFoundException

I have a pre-existing Oracle database that I want to exploit.
To do this, I created 2 classes that I map with many-to-one and one-to-many. But there are elements of which the first class have no correspondence in the second class, however id exists. It does not return any error.
I would like that it sends me rather a null. How to do that ?
class Fact
{
/**
*
* #ORM\OneToMany(targetEntity="present\UserBundle\Entity\March",mappedBy="fact", cascade={"persist","remove"})
*/
private $march;
}
class March
{
...
/*
* #var \present\PublishBundle\Entity\image
* #ORM\ManyToOne(targetEntity="present\UserBundle\Entity\fact",inversedBy="march", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id",referencedColumnName="id")
* })
* })
*/
private $facture;
}
the error
Entity of type 'present\UserBundle\Entity\Client' for IDs id(0) was not found
Thanx you
I haven't tested your code but I think your error has to do your JoinColumn declaration :
#ORM\JoinColumn(name="id",referencedColumnName="id")
In the documentation here it says that the name attribute will define the name of the column for your relation.
But here "id" is also referencing to your primary key I suppose.
To solve it, try to change the name="id" by name="march_id", or remove the JoinColumn.
Edit : I have read a bit to quickly the error, haven't seen it's referencing to Client entity, can you show the code related to this relation too ?

PHPDoc: append methods to the current class, as if it were extending a class [duplicate]

Is there a way to document that a certain class has magic methods for every method defined in another class?
I am using PhpStorm, so I would be happy with any solution that will get autocomplete to work properly for that.
class A
{
// a bunch of functions go here...
}
/**
* Class B
* What should go here to make it work???
*/
class B
{
private $aInstance;
public function __construct() {
$this->aInstance = new A();
}
public function __call($name, $arguments) {
// TODO: Implement __call() method.
if(method_exists($this->aInstance, $name)) {
return $this->aInstance->{$name}(...$arguments);
}
throw new BadMethodCallException();
}
// a bunch more functions go here...
}
The proper solution is to use supported #method PHPDoc tags. This way it will also work in other editors/IDEs that support PHPDoc and understand such standard tag.
This approach requires every method to be listed separately. More on this in another StackOverflow question/answer: https://stackoverflow.com/a/15634488/783119.
In current PhpStorm versions you may use not-in-PHPDoc-specs (and therefore possibly PhpStorm-specific) #mixin tag.
Adding #mixing className in PHPDoc comment for your target class should do the job for you.
/**
* Class B
*
* #mixin A
*/
class B
{
Basically, #mixin tag does what actual PHP's traits do.
Please note that there is no guarantee that support for such tag will not be removed at some point in the future, although it's pretty unlikely.

Laravel 5.0 -Dev Defining Global Patterns is not working

Laravel Docs provide a way to add global patterns inside the before function inside RouteServiceProvider.php.
my question is : by default there is no such function, besides, after creating it, it's not working!
/**
* Define global rules for routes.
* more specially for regullar expressions.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
public function before(Router $router){
$router->pattern('id', '[1-9]+[0-9]*');
}
Just in case you're still interested (I dont have enough points to comment), you can still declare it inside boot() but just before the $route variable is passed to the parent parent::boot($router); function, like here:
/**
* Define your route model bindings, pattern filters, etc.
*
* #param \Illuminate\Routing\Router $router
* #return void
*/
public function boot(Router $router)
{
///////////////////////////
// route global patterns //
///////////////////////////
$router->pattern('id', '[0-9]+');
parent::boot($router);
}
This worked for me
I had the same problem and what I did was to append the before() method body into the map() method body. It worked for me. :)

Symfony2 file upload step by step

I am still learning Symfony2 and don't understand how to upload a file.
Don't worry, I've already checked the documentation. It's really good, but my problem isn't explained in any tutorial.
I am looking for guidance on how to upload a file with Symfony2 but with all the thing everybody needs (such as constraint of extension, rename of the file based on the id and stuff, store the path in the db, etc...)
I found good tutorials, tried to mixed them but with no success. Each time a different problem appears: file re-uploads on every submit on the form (even if the file field is empty), guessExtension impossible to used, tmp path stored in the database instead of the right path, file not moved, impossible to used the id in the rename because the id is on auto-increment and so not yet generated).
So, I'll put an 'standard' entity, let say: Photo.php
/**
* Photo
*
* #ORM\Table(name="photo")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Photo
{
// Annotation for the id and auto increment etc
private $id;
/**
* #var string
* #Assert\File( maxSize = "3072k", mimeTypesMessage = "Please upload a valid Image")
* #ORM\Column(name="image", type="string", length=245, nullable=false)
*/
private $image
private $title
private $description
// all the function get, set for the 4 previous variables
}
and the controller:
public function addPhotoAction()
{
$add_photo = new Photo;
$formBuilderPhoto = $this->createFormBuilder($add_photo);
$formBuilderPhoto
->add('title','text',array('label' => 'Title of the photo', 'required' => true))
->add('image','file', array('required' => true, 'data_class' => null))
->add('description','textarea',array('label' => 'Description of your photo', 'required' => false))
;
$form_photo = $formBuilderPhoto->getForm();
if ($request->getMethod() == 'POST') {
$form_photo->bind($request);
if ($form_photo->isValid()) {
// ...
}
}
return $this->render('MyBundle:frontend:photo.html.twig',
array('form_photo' => $form_photo->createView())
);
}
Do you know now what are the 'important' function to add to be able to upload the photo and rename it ?
How do you check the extension to see if the upload is possible?
What is your actual way of doing such a thing with Symfony2? I know there is a lot of Bundle that do all those things for you but I want to learn to do it and understand the process.
What is the 'classic' way to implement a file upload form and rename function with Symfony2?
Do you know now what are the 'important' function to add to be able to upload the photo and rename it?
See the official documentation on how to do this. There are good working examples for a simple file upload. Also check the doctrine documentation for lifecycle callbacks.
How do you check the extension to see if the upload is possible?
There is some HTML-Form validation in each browser. See this question for the HTML accept="" attribute in input elements. Also in Symfony2 you can specify the MIME-type of an uploaded file using this annotation:
/**
* #Assert\File(
* maxSize = "1024k",
* mimeTypes = {"application/pdf", "application/x-pdf"},
* mimeTypesMessage = "Please upload a valid PDF"
* )
*/
Even though you did not want to use any bundles I'll have to recommend you the KnpDoctrineBehavioursBundle which makes file uploading way easier.
Step-by-step:
Because you read the documentation already I'll give you a step by step code-example.
First of all you need an entity. Let's call it Image:
/**
* Class Image
*
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Image extends BaseEntity
{
Note the #ORM\HasLifecycleCallbacks annotation. It is very important and you need it later. We create all the basic fields like ID and what not. Also we need a field to store the file path in:
/**
* Image path
*
* #var string
*
* #ORM\Column(type="text", length=255, nullable=false)
*/
protected $path;
And one for the Image itself. Here we also define the Validation for the images. In my example it has to be 5M big and of one of the defined mimeTypes. It should be self-explanatory. Otherwise the official docs help as always.
/**
* Image file
*
* #var File
*
* #Assert\File(
* maxSize = "5M",
* mimeTypes = {"image/jpeg", "image/gif", "image/png", "image/tiff"},
* maxSizeMessage = "The maxmimum allowed file size is 5MB.",
* mimeTypesMessage = "Only the filetypes image are allowed."
* )
*/
protected $file;
Add all the Getters & Setters and update your database schema with this command:
php app/console doctrine:schema:update --force
Next we need the lifecycles. They are methods in the Entity that are called on certain events. For example the #ORM\PreUpdate() annotation before a method says that this method is being called right before the entity gets updated.
/**
* Called before saving the entity
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->path = $filename.'.'.$this->file->guessExtension();
}
}
Before the entity gets stored or updated this method is called. You can use it to e.g. generate a unique file name.
/**
* Called before entity removal
*
* #ORM\PreRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
Called before the entity gets removed. This gives you time to delete the image from your folders or log a message if you want to.
/**
* Called after entity persistence
*
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
// The file property can be empty if the field is not required
if (null === $this->file) {
return;
}
// Use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->file->move(
$this->getUploadRootDir(),
$this->path
);
// Set the path property to the filename where you've saved the file
//$this->path = $this->file->getClientOriginalName();
// Clean up the file property as you won't need it anymore
$this->file = null;
}
This is the important part where your file is actually moved to the right directory. Note that I have used some additional methods. You can all get them from the official docs.
Next thing you need is a form. The form class itself is very simple. Just make sure you set the default data_class like this:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'FSchubert\SiyabongaBundle\Entity\Image',
)
);
}
A file upload field can be created very easily in the buildForm() method:
$builder->add('file', 'file');
The methods for your Controller are a little long for just pasting them here and IMHO it's not part of answering your question. There are countless examples out there for writing a proper Controller Action for your purpose.
More things you have to keep in mind:
You need to give your app writing permissions to the folders you upload the files to. Although it seems obvious it can be annoying if you have multiple servers you run the application on.
There is an Image Constraint for your entity as well. You can find it here. But since you were talking about a file upload I used the File Constraint instead.
As I mentioned in the top of this post, there are many Bundles that handle all these things for you. Check them out if you want an easy life.
Edit:
Changed from DoctrineExtensionsBundle to DoctrineBehaviours since development on the old one stopped in favour of the DoctrineBehaviours bundle.
I recommend you to use vlabs media bundle.
The VichUploaderBundle is also easy to use for uploading files:
https://github.com/dustin10/VichUploaderBundle
I recommend VichUploader bundle and this code with the bundle implanted in the entity and the FormType.
composer require vich/uploader-bundle
Admission.php
/**
* #ORM\Entity(repositoryClass=AdmissionRepository::class)
* #Vich\Uploadable
*/
class Admission
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $cin;
/**
* #Vich\UploadableField(mapping="product_image", fileNameProperty="cin")
* #var File
*/
private $cinFile;
public function getId(): ?int
{
return $this->id;
}
public function getCin(): ?string
{
return $this->cin;
}
public function setCin(string $cin): self
{
$this->cin = $cin;
return $this;
}
}
AdmissionType.php
class AdmissionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('cinFile', VichFileType::class);
}
vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
product_image:
uri_prefix: /uploads
upload_destination: '%kernel.project_dir%/public/uploads'
inject_on_load: false
delete_on_update: true
delete_on_remove: true
namer: vich_uploader.namer_origname

Which mapping type to choose for associative Arrays? Doctrine ODM

I have a simple question about the (by the way really great!) Doctrine ODM.
Assume you have a document like:
/**
* #Document
*/
class Test
{
/** #Id */
public $id;
/** #WHICHTYPE */
public $field = array();
}
Now i want to store an associative array like
array("test" => "test1", "anothertest" => "test2", ......);
In the $field property of that class.
No problem for MongoDB, I know, but in Doctrine when I use for example #Collection or simply #Field, only the values are stored (array_values is being used in the mapping driver for collection for example). So the stored value looks like
array("test1", "test2", ....)
Does anyone know which Doctrine-ODM mapping type I should use in order to preserve the key-value pairs in the database?
Thank you in advance,
Andi (greetz from germany)
It should be the Hash type:
http://readthedocs.org/docs/doctrine-mongodb-odm/en/latest/reference/annotations-reference.html?highlight=hash#hash
For versions before ODM 2.0 #Hash will provide the necessary data type. However after ODM 2.0 #Hash field is being removed. In order to use it we have to use #field with type hash.
For further reference [click here][1]
I think you're looking for hash data type. Aren't you?
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #Document
*/
class Test
{
/** #Id */
public $id;
/**
* #MongoDB\Field(type="hash")
*/
public $field;
}
The best answer is using hash type. But if for some reason you wantn't use hash type, you can use EmbeddedDocument feature provided by Doctrine ODM like the documentation says:
If you are using the hash type, values within the associative array
are passed to MongoDB directly, without being prepared. Only formats
suitable for the Mongo driver should be used. If your hash contains
values which are not suitable you should either use an embedded
document or use formats provided by the MongoDB driver (e.g.
\MongoDate instead of \DateTime).
So, you need to create EmbeddedDocument EmbeddedExample in AppBundle\Document\EmbeddedExample.php:
<?php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\EmbeddedDocument()
*/
class EmbeddedExample
{
/**
* #MongoDB\Field(type="int")
*/
protected $some_name;
// ...
// getter and setter
}
Then, you can use EmbeddedExample in your Test document. So the Test.php file will be similar to this:
<?php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document(repositoryClass="AppBundle\Repository\TestRepository")
*/
class Test
{
/** #MongoDB\EmbedOne(targetDocument="EmbeddedExample") */
private $field;
// ...
}
#Array should work. At least an equivalent exists in the ORM (#Column(type="array"))

Resources