I have a File entity for handle files upload in other entities (news/blog/etc).
I point to it with a OneToOne relation and it works fine. But I would change the upload dir, for each relation entity :
upload/news
upload/blog
The upload path is set in my file entity so i dont know how to automaticaly update the path foreach relations...
do you have an idea on how to do it ?
Thanks
Of course you can do it.
On your file entity side, you can add a uploadDir attribute, and create a setter like this :
private $uploadDir;
public function setUploadDir($uploadDir)
{
if (!$this->uploadDir) {
$this->uploadDir = $uploadDir;
}
}
your comment suggest you use Symfony with Doctrine right ?
So you can edit the classical getUploadDir() method like this:
protected function getUploadDir()
{
return 'uploads/' . $this->uploadDir;
}
In the 'parent' entity you have to update this attribute (when it is created) before persist or update.
(I personally use life cycle callbacks but you can do it manually in your controller)
use Doctrine\ORM\Mapping as ORM;
/**
* News
*
* #ORM\Table(name="News")
* #ORM\HasLifecycleCallbacks
*/
class News
{
//....
/**
* #ORM\OneToOne(targetEntity="File",cascade={"persist","remove"})
*/
private $file;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
function setUploadDir()
{
$this->getFile()->setUploadDir('news');
}
// ....
You can also add a constant to make the code cleaner...
I hope it'll help you
Related
I need to change the queried table from a Laravel-model right before the query begins.
Normally you make a query like this:
ExampleModel::where('column_name', =, 'value')->get();
For one case I want to use a view-table which contains information from multiple tables combined in one view.
Therefore I need to switch the table of ExampleModel only for this one situation, e.g.:
ExampleModel::table('my_view')->where(...)->get();
It is not an option to use DB::table('my_view')->where(...)->get() because of several local scopes which need to be applied on ExampleModel.
As I could see, there are the following options:
somehow change the models table-name on the fly (as shown above)
Create a new Model used only in this use case which has the view defined as model-table
write all my scopes into a chained DB-command
Are there any other options?
The laravel way to Handle this would be to have a dedicated model for your view, with Scopes applying to each of the relevant models :
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('age', '>', 200);
}
}
Then in your ExampleModel AND you MyView Model
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
So that when you want to edit a scope it will be reflected on each of your queries
BUT
You will allways know if your model is from your view_table or from you example_model table.
If you need to have some accessor or function used by both, I recomand you to put them in a Trait and use them on both Models
trait ExampleModelTrait
{
getTestAttribute(){
return strtolower($this->column_name);
}
}
and then
use ExampleModelTrait;
you can do this stuff by passing table name to setTable() method for example :
$user = new Users();
$user->setTable('customers');
$user->where(id,1)
...
I have problem with changing parent of document inside sonata admin.
My document has property like this:
/**
* #PHPCRODM\Children(filter="teaserimage*", cascade="persist")
* #var Image[]
*/
protected $teaserImages;
....
/**
* #Validation\PhpcrImage(maxWidth="1500", maxHeight="844", minWidth="600", minHeight="338", minRatio="1.77", maxRatio="1.78", nullable=false)
*
* #return Image|null
*/
public function getTeaserImage()
{
return $this->getLocalizedImage($this->teaserImages, 'teaserimage');
}
/**
* #param Image $image
*/
public function setTeaserImage($image)
{
$this->teaserImages->add(
self::setupImage($image, $this->getLocalizedImageNodeName('teaserimage'), $this->getTeaserImage())
);
}
When i tried to change parent of any article document i got error like
The server returned a "500 Internal Server Error".
The Id is immutable (/cms/content/blog/blog-post-for-12th-october/teaserimage_de.jpg !== /cms/content/blog/blog-post-for-12th-october). Please use DocumentManager::move to move the document: blog post for 12th October (/cms/content/blog/blog-post-for-12th-october)
Although this error occurs, my document has been moved to the right place with all subdocument.
this is how my document looks like
https://gist.github.com/milosnovi/a83f400c8ff06b4de6dd96e1f149a8dd
Check your preUpdated, prePersists methods. You shouldn't flush object in these methods while the change is a parent change.
I'm totally new to the CakePhp framework, so I'm doing the basic tutorial. So far so good, I built the scaffolding for my models, authentication works fine, but I't a little excessive: I would like to allow one action ('index' for example) to be allowed even for non-authenticated users.
I suspect it must have something with "BeforeFilter()", but any solution I tried hasn't worked - probably because they'for CPHP 2.0, and/or I'm dumb
.
The code is here, though it's not particuolarily interesting since it's the one generated by the scaffolding mechanism.
<?php
namespace App\Controller;
use App\Controller\AppController;
/**
* Frutta Controller
*
* #property \App\Model\Table\FruttaTable $Frutta
*/
class FruttaController extends AppController
{
/**
* Index method
*
* #return void
*/
public function index()
{
$this->set('frutta', $this->paginate($this->Frutta));
$this->set('_serialize', ['frutta']);
}
//cut..
}
Use the following:
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('index'); //allow index without authentication
}
Reference : http://book.cakephp.org/3.0/en/controllers/components/authentication.html#making-actions-public
We are familiar with Components and Helpers in CakePHP.
I have an ABC Component and XYZ helper and both have same function (around 2000 lines total 4000 lines).
there is any way to use same function in Controller and .CTP files. it's not good to use same function 2 time.
any method so i can use Component/Helper function in Helper/Component without rewrite ?
same method into component and helper >>
Component
class DATAComponent extends Component {
public $components = array('Session', 'THmail');
public function UsaStateList()
{ /********/}
Helper
class LabHelper extends AppHelper {
public function UsaStateList()
{ /********/}
}
Well, you will have to rewrite something, it's not going to solve itself.
CakePHP is still PHP, so you can easily apply common patterns to keeps things DRY. The most straight forward way would probably be to move the shared functionality into an utility class that your component and helper can both use internally while leaving their public API unchanged.
Some of CakePHPs helpers do something similar, check for example the time helper.
http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html
http://book.cakephp.org/2.0/en/core-utility-libraries/time.html#CakeTime
Traits might be an option too, depending on the amount of functionality being shared and how much it is tied to the use in a component/helper.
I wanted to use a component inside a helper. So i came out with the following solution.
<?php
App::uses('AppHelper', 'View/Helper');
App::import('Component', 'MyComponent');
class MyHelperHelper extends AppHelper {
private $MyComponent = null;
public function __construct(View $View, $settings = array()) {
$collection = new ComponentCollection();
$this->MyComponent = new MyComponentComponent($collection);
parent::__construct($View, $settings);
}
public function myCustomFunction() {
return $this->MyComponent->myCustomFunction();
}
}
Simple Answer
For common functions across your application, add a Lib or Utility class.
app/Lib/MyClass.php
class MyClass {
public static function usaStateList() {
// ...
}
}
Then add this at the top of whichever file you want access to the function:
App::uses('MyClass', 'Lib');
And call your function wherever you like:
$myClass = new MyClass();
$states = $myClass::usaStateList();
Better Answer
It looks to me like your specific problem is that you want to be able to get a list of US states in both your controller and your view. The best way to do this is to have a database table of US states.
Create a table in your database called us_states.
Example SQL:
CREATE TABLE `us_states` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(20) NOT NULL,
`abbreviation` CHAR(2) NOT NULL
) ENGINE = MYISAM
Insert all the states as data in that table. You can find SQL dumps on the Internet which already have that for you (e.g. Like this one).
Create a UsState model in CakePHP.
/**
* Model for US States
*
* #package app.Model
*/
class UsState extends AppModel {
/**
* Default sort order
*
* #var string|array
*/
public $order = 'UsState.name';
}
What you can then do is access the states from your controller just by using the model.
/**
* Your Controller
*
* #package app.Controller
*/
class YourController extends AppController {
public function index() {
// Get a list of US states
$this->loadModel('UsState');
$states = $this->UsState->find('all');
}
}
And if you want to access those states in your View, then you should pass along that data as you normally would any other variables.
I imagine you would want to do that so you can have a select menu of US states, perhaps.
public function index() {
// Get a list of US states
$this->loadModel('UsState');
$states = $this->UsState->find('all');
// Make the states into an array we can use for a select menu
$stateOptions = array();
foreach ($states as $state) {
$stateOptions[$state['id']] = $state['name'];
}
// Send the options to the View
$this->set(compact('stateOptions'));
}
And in your view you can display a select menu for that like this:
echo $this->Form->select('us_state_id', $stateOptions);
I would go with a class in Lib folder. It is pretty clear how to deal with a library class that has only static methods. So, I omit this case. A workable solution for instantiating the class could be to create it in the controller and put it into the registry. If you really need to access CakeRequest, CakeResponse and CakeSession (take a note that CakeSession has many static methods, so you often do not need an instance of that class) from that class you can set it from the controller:
$MyLib = new MyLib();
$MyLib->setRequest($this->request); // optional
ClassRegistry::addObject('MyLib', $MyLib);
Then from the view or any other place you would just get an instance of MyLib from the registry:
ClassRegistry::getObject('MyLib');
or simply
$list = ClassRegistry::getObject('MyLib')->UsaStateList();
So, your class would be something like this:
// /Lib/MyLib.php
class MyLib {
public function setRequest(CakeRequest request) {...}
public function UsaStateList() {...}
}
ok you want to use a single function in component and helper, I can think of 3 things you can do:
Calling a function from the component in your helper.
Calling a function from a helper in your component.
Create a model or use an existing model where you put the function, and you can use this function in your component or your help.
Option numbre 3:
In your helper and component, you need import a model, assuming that your function be in a model "StateList":
how you call the funcion of the model "StateList" in your helper, so:
App::import("Model", "StateList");
$model = new StateList();
$model->UsaStateList();
how you call the funcion of the model "StateList" in your component, so:
$model = ClassRegistry::init('StateList');
$model->UsaStateList();
ans if you want use the same function in a controller just:
$this->loadModel('StateList');
$this->StateList->UsaStateList();
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