Doctrine 2: Oracle Insert Trigger - oracle

i currently started to work with the symfony2 framework and doctrine2.
I have the following problem here with using doctrine when I want to insert an object.
Following given situation:
The table uses a sequence, that generates the ID via BEFORE INSERT
Trigger
The sequence is uses by other tables as well, so there is no name
convention like: $table->getName() . '_SEQ'; (see
Doctrine/DBAL/Platforms/OraclePlatform.php getIdentitySequenceName())
The sequence that should be uses id called: general_data_seq
The design of the tables and triggers is given and cannot be changed.
I have made the following entity:
/**
* #ORM\Entity
* #ORM\Table(name="test")
*
*/
class Test
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return string
*/
public function getName() : string
{
return $this->name;
}
/**
* #return string
*/
public function setName(string $name)
{
$this->name = $name;
}
}
If i want to save this using doctrine like this
$em = $this->getDoctrine()->getManager();
$test = new Test();
$test->setName('Max');
$em->persist($test);
$em->flush();
I'm getting the message:
ORA-02289: sequence does not exist.
What is true, since the sequence that should be use really does not exist.
My question now is: How to use docrine here so that the persisted entity has the inserted id generated by the trigger?
I found a related problem here:
Doctrine 2: cascade persist Oracle "IDENTITY" is returning 0 as last inserted ID
and here: https://github.com/doctrine/dbal/issues/1772
but nighter of them helped me to find a solution, since they are still bases on using the table-named-sequence.

Related

What is the best way for reusable values throughout the application in Symfony 3?

I want to have a file or list that I can update easily with values that might change throughout my application.
I don't really want to hard code text values into the templates. I prefer to have all of these values in one place and labelled correctly.
Examples of values that might get updated are:
Page title
Logo text
Brand or company name
I have thought about two options:
Add them to the twig config in config.yml. This is a bit messy and doesn't seem organised if I decide to put a lot of values there.
Make a database table for these and include the entity in each controller where I need to use the values. This might be creating too much work.
Are there any other options or are one of these more suitable?
Thank you.
You need to create a twig function and use it to return the value you want. For example:
namespace AppBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TwigExtension extends \Twig_Extension implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #var ContainerInterface
*/
protected $container;
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('parameter', function($name)
{
try {
return $this->container->getParameter($name);
} catch(\Exception $exception) {
return "";
}
})
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'app.twig.extension';
}
}
This will create a function called parameter and once you call it in twig {{ parameter('my.parameter') }} it will return the parameter. You need to load it as a service, which you can do by adding the following to your services.yml file:
app.twig.extension:
class: AppBundle\Twig\TwigExtension
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: twig.extension }
From personal experience people usually want to be able to change some of the parameters. This is why I usually prefer to create a Setting or Parameter entity which would look something like this:
/**
* Setting
*
* #ORM\Table(name="my_parameters")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ParameterRepository")
*/
class Parameter
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="parameter_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=true)
*/
private $value;
/**
* #param string|null $name
* #param string|null $value
*/
public function __construct($name = null, $value = null)
{
$this->setName($name);
$this->setValue($value);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Parameter
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set value
*
* #param string $value
*
* #return Parameter
*/
public function setValue($value = null)
{
$this->value = serialize($value);
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
$data = #unserialize($this->value);
return $this->value === 'b:0;' || $data !== false ? $this->value = $data : null;
}
}
Then I would add a CompilerPass which will help get all of the parameters from the database and cache them so that your app doesn't make unnecessary sql queries to the database. That might look something similar to the following class:
// AppBundle/DependencyInjection/Compiler/ParamsCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParamsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$settings = $em->getRepository('AppBundle:Parameter')->findAll();
foreach($settings as $setting) {
// I like to prefix the parameters with "app."
// to avoid any collision with existing parameters.
$container->setParameter('app.'.strtolower($setting->getName()), $setting->getValue());
}
}
}
And finally, in your bundle class (i.e. src/AppBundle/AppBundle.php) you add the compiler pass:
namespace AppBundle;
use AppBundle\DependencyInjection\Compiler\ParamsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $builder)
{
parent::build($builder);
$builder->addCompilerPass(new ParamsCompilerPass(), , PassConfig::TYPE_AFTER_REMOVING);
}
}
Now you can create a DoctrineFixture template to load the parameters you use all the time. With the TwigExtension you will still be able to call the parameter from the twig template and you can create a web UI to change some of the parameters/settings.

Custom generator command, is not stopping when create file that already exists

So, i'm trying to use PHP Artisan on Laravel 5.3 to create a class file for each Cron configuration in my project, i'm doing this because it's possible that i'll want to create these files from a separate GUI in the future.
I'm able to create the files, and i'm using stubs so everything gets generated as it should, the problem however is that for some reason, if a file, say "cron_4" exists and i call my custom command php artisan make:cron cron_4 it'll allow me to do so and will simply overwrite the existing file.
This is my code so far. Any ideas as to what i might be doing wrong here?
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class CronMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* #var string
*/
protected $name = 'make:cron';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Create a new Cron class';
/**
* The type of class being generated.
*
* #var string
*/
protected $type = 'Cron';
/**
* Get the stub file for the generator.
*
* #return string
*/
protected function getStub()
{
return __DIR__.'/stubs/cron.stub';
}
/**
* Get the default namespace for the class.
*
* #param string $rootNamespace
* #return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Crons';
}
/**
* Execute the console command.
*
* #return void
*/
public function fire()
{
if (! $this->option('id')) {
return $this->error('Missing required option: --id');
}
parent::fire();
}
/**
* Replace the class name for the given stub.
*
* #param string $stub
* #param string $name
* #return string
*/
protected function replaceClass($stub, $name)
{
$stub = parent::replaceClass($stub, $name);
return str_replace('dummy:cron', 'Cron_' . $this->option('id'), $stub);
}
/**
* Determine if the class already exists.
*
* #param string $rawName
* #return bool
*/
protected function alreadyExists($rawName)
{
return class_exists($rawName);
}
/**
* Get the console command options.
*
* #return array
*/
protected function getOptions()
{
return [
['id', null, InputOption::VALUE_REQUIRED, 'The ID of the Cron being Generated.'],
];
}
}
I figured it out, it was my custom code that was to blame
/**
* Determine if the class already exists.
*
* #param string $rawName
* #return bool
*/
protected function alreadyExists($rawName)
{
return class_exists($rawName);
}
This was overriding the default configurations which made it fail probably because of the $rawName variable.
In my case simply removing this function solved the issue.

Yii- Caching with CSqlDataprovider

Is it possible to do caching of data from sql server queries when using CSqlDataProvider. If so can anyone please provide some links for documentation about it. Or if you have done it personally please guide.
I did a search but found nothing :(
There is some example of implementing this feature
<?php
class CachedSqlDataProvider extends CDataProvider
{
public $queryCache;
public $queryCacheLife;
/**
* #var CDbConnection the database connection to be used in the queries.
* Defaults to null, meaning using Yii::app()->db.
*/
public $db;
/**
* #var string the SQL statement to be used for fetching data rows.
*/
public $sql;
/**
* #var array parameters (name=>value) to be bound to the SQL statement.
*/
public $params=array();
/**
* #var string the name of key field. Defaults to 'id'.
*/
public $keyField='id';
/**
* Constructor.
* #param string $sql the SQL statement to be used for fetching data rows.
* #param array $config configuration (name=>value) to be applied as the initial property values of this class.
*/
public function __construct($sql,$config=array())
{
$this->sql=$sql;
foreach($config as $key=>$value)
$this->$key=$value;
}
/**
* Fetches the data from the persistent data storage.
* #return array list of data items
*/
protected function fetchData()
{
$sql=$this->sql;
$db=$this->db===null ? Yii::app()->db : $this->db;
$db->active=true;
if(($sort=$this->getSort())!==false)
{
$order=$sort->getOrderBy();
if(!empty($order))
{
if(preg_match('/\s+order\s+by\s+[\w\s,]+$/i',$sql))
$sql.=', '.$order;
else
$sql.=' ORDER BY '.$order;
}
}
if(($pagination=$this->getPagination())!==false)
{
$pagination->setItemCount($this->getTotalItemCount());
$limit=$pagination->getLimit();
$offset=$pagination->getOffset();
$sql=$db->getCommandBuilder()->applyLimit($sql,$limit,$offset);
}
if( $this->queryCache == true && $this->queryCacheLife > 0 )
$command=$db->cache( $this->queryCacheLife )->createCommand($sql);
else
$command=$db->createCommand($sql);
foreach($this->params as $name=>$value)
$command->bindValue($name,$value);
return $command->queryAll();
}
/**
* Fetches the data item keys from the persistent data storage.
* #return array list of data item keys.
*/
protected function fetchKeys()
{
$keys=array();
foreach($this->getData() as $i=>$data)
$keys[$i]=$data[$this->keyField];
return $keys;
}
/**
* Calculates the total number of data items.
* This method is invoked when {#link getTotalItemCount()} is invoked
* and {#link totalItemCount} is not set previously.
* The default implementation simply returns 0.
* You may override this method to return accurate total number of data items.
* #return integer the total number of data items.
*/
protected function calculateTotalItemCount()
{
return 0;
}
}
?>

how to sort an entity's arrayCollection in symfony2

I have an entity "container" with this property
/**
* #ORM\OneToMany(targetEntity="BizTV\ContentManagementBundle\Entity\Content", mappedBy="container")
*/
private $content;
the property is an array collection...
public function __construct() {
$this->content = new \Doctrine\Common\Collections\ArrayCollection();
}
...with these two standard methods
/**
* Add content
*
* #param BizTV\ContentManagementBundle\Entity\Content $content
*/
public function addContent(\BizTV\ContentManagementBundle\Entity\Content $content)
{
$this->content[] = $content;
}
/**
* Get content
*
* #return Doctrine\Common\Collections\Collection
*/
public function getContent()
{
return $this->content;
}
Now my question is, is there a smooth way to build a sorting feature into this, perhaps on the getContent() call? I am no php wiz and certainly not seasoned in symfony2 but I learn as I go.
The content entity itself has a sorting INT like this that I want to sort it on:
/**
* #var integer $sortOrder
*
* #ORM\Column(name="sort_order", type="integer")
*/
private $sortOrder;
You should be able to use the #ORM\OrderBy statement which allows you to specify columns to order collections on:
/**
* #ORM\OneToMany(targetEntity="BizTV\ContentManagementBundle\Entity\Content", mappedBy="container")
* #ORM\OrderBy({"sort_order" = "ASC"})
*/
private $content;
In fact this may be a duplicate of How to OrderBy on OneToMany/ManyToOne
Edit
Checking for implementation advice it appears that you must fetch the tables with a join query to the collection in order for the #ORM\OrderBy annotation to work: http://www.krueckeberg.org/notes/d2.html
This means that you must write a method in the repository to return the container with the contents table joined.
If you want to be sure that you always get your relations in the order based on current property values, you can do something like this:
$sort = new Criteria(null, ['Order' => Criteria::ASC]);
return $this->yourCollectionProperty->matching($sort);
Use that for example if you've changed the Order property. Works great for a "Last modified date" as well.
You can write
#ORM\OrderBy({"date" = "ASC", "time" = "ASC"})
for multiple criteria ordering.
You can also sort ArrayCollection by Criteria property orderBy like so:
<?php
namespace App/Service;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
/**
* Class SortService
*
* #package App\Service
*/
class SortService {
/**
* #param SomeAbstractObject $object
* #return SomeCollectionItem[]
*/
public function sorted(SomeAbstractObject $object): array {
/** $var ArrayCollection|SomeCollectionItem[] */
$collection = $object->getCollection();
// convert normal array to array collection object
if(\is_array(collection)) {
$collection = new ArrayCollection(collection);
}
// order collection items by position property
$orderBy = (Criteria::create())->orderBy([
'position' => Criteria::ASC,
]);
// return sorted SomeCollectionItem array
return $collection->matching($orderBy)->toArray();
}
}
?>

Symfony2: PrePersist/PreUpdate lifecycle-event not fired

Two Entities GalleryAlbum and GalleryImage have OneToMany/ManyToOne relationship:
One GalleryAlbum ==== can have ====> Many GalleryImage
Many GalleryImage === can be in ===> One GalleryAlbum
(sources below)
What is the problem?
Adding (uploading) files to GalleryAlbum
$em->persist($album)
$em->flush()
For each uploaded file GalleryAlbum class creates and adds to $images a new GalleryImage entity
My ECHO/EXIT test is not shown (GalleryImage's prePersist/preUpdate event callback function named preUpload is not triggered!)
My new images are not saved to the database? Why?
What is weird! If I do:
Adding (uploading) files
$em->persist($album)
$em->flush()
again $em->flush()
My ECHO/EXIT test is shown (GalleryImage's prePersist/preUpdate event callback function named preUpload is triggered!)
(if i delete echo/exit) My new GalleryImages are saved now!!!
Why?
Why is preUpload never triggered when I flush() once, and is triggered when I flush() twice?
# src GalleryAlbum.php
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="gallery_album")
*/
class GalleryAlbum
{
// some properties like id, name, description, etc
/**
* #ORM\OneToMany(targetEntity="GalleryImage", mappedBy="parent")
*/
protected $images;
/* Files container. Used for upload service. Must not be persisted. */
protected $files;
/* #ORM\Column(type="boolean", nullable=TRUE)
*
* if set to true will updateing object and calling preUpdate event callback
* becouse it's always set to null in database by prePersist event callback */
protected $files_added;
/**
* Set container files
*
* #return GalleryAlbum
*/
public function setFiles($files)
{
$this->files = $files;
$this->files_added = true;
/* setting files_added to true forces EntityManager to update
* this GalleryAlbum even if no other properties have changed */
return $this;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if(null !== $this->files) {
foreach($this->files as $key => $file) {
$this->addGalleryElement($file);
unset($this->files[$key]);
}
}
/* Resetting property files_added to NULL
* so it always stays null in database */
$this->files_added = null;
}
/**
* Constructing new GalleryImage and setting it's file and parent
*/
public function addGalleryElement($file)
{
$element = new GalleryImage($this, $file);
$this->addGalleryImage($element);
}
}
# src GalleryImage.php
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="gallery_image")
*/
class GalleryImage
{
// some properties like id, name, description, etc
/**
* #ORM\ManyToOne(targetEntity="GalleryAlbum", inversedBy="images")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
protected $parent;
/* Constructing new GalleryImage */
public function __construct($parent = null, $file = null)
{
if($parent) $this->setParent($parent);
if($file) $this->setFile($file);
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
echo 'TEST: is this event callback function fired?'; exit;
if(null !== $this->file) {
$this->path = $this->file->guessExtension();
}
$this->file_added = null;
}
}
The first time you call persist doctrine will only save $album and not its images. You must specify that you want doctrine to cascade the persist by specifying it in the $images declaration:
/**
* #ORM\OneToMany(targetEntity="GalleryImage", mappedBy="parent", cascade={"persist", "remove"})
*/
protected $images;
This way when you call persist($album) it will also persist your images and should trigger preUpload in your GalleryImage. There are a few different options available for cascading and they are explained well here:
Doctrine transitive persistence cascade operations

Resources