Following Doctrine 2's enum defining a type guide, I have the following class:
class EnumStatusType extends EnumType
{
protected $name = 'enumStatusType';
protected $values = [
'active',
];
}
Now, using vendor/bin/doctrine-module migrations:diff, or vendor/bin/doctrine-module orm:schema-tool:update or whichever you prefer, it successfully creates the column with the enum:
status ENUM(\'active\') COMMENT \'(DC2Type:enumStatusType)\' NOT NULL
Now, I wanted to add a second value, inactive. But after running orm:validate-schema, orm:schema-tool:update migrations:diff, none of them notices there is a new value.
How can I make it so that it detects this type of changes, so that a new migration can be made with migrations:diff?
PS: I'm using ZF2, with the DoctrineORMModule. Not that it should matter though.
You can try adding the enum values list in each field comment option, using the postGenerateSchema event:
class EnumListener
{
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
{
$columns = [];
foreach ($eventArgs->getSchema()->getTables() as $table) {
foreach ($table->getColumns() as $column) {
if ($column->getType() instanceof EnumType) {
$columns[] = $column;
}
}
}
/** #var \Doctrine\DBAL\Schema\Column $column */
foreach ($columns as $column) {
$column->setComment(trim(sprintf('%s (%s)', $column->getComment(), implode(',', $column->getType()->getEnum()::toArray()))));
}
}
}
Works for the orm:schema-tool:update command, I suppose it's the same for migrations:diff
Related
I have a problem using Doctrine with many to many relation when I persist my data.
I have 2 entities :
- Bloc
- Job
I retrieve my ID's before the persist with getJobs() method.
$bloc = $form->getData();
// $bloc->getJobs() works I retrieve good IDs with foreach getId()
$em->persist($bloc);
$em->flush();
My method addJob :
/**
* Add jobs
*
* #param Job $jobs
* #return Bloc
*/
public function addJob(Job $jobs)
{
$this->jobs[] = $jobs;
return $this;
}
My form:
$bloc = $em->getRepository('Acme\\Entity\\Bloc')->find($id);
$form = $this->createForm(BlocType::class, $bloc);
$form->handleRequest();
if($form->isSubmitted()) {
$bloc = $form->getData();
$em->persist($bloc);
$em->flush();
$this->addFlashMessage('edit', "Update message");
}
But the persist add new line in my job entity instead of use line already in my database.
Any idea?
I guess $jobs is a Collection of Job, so you should implements methods to handle this collection.
If your entity declarations are correct (oneToMany, ManyToOne, uni/bi directional) you should have something like this :
class Bloc
{
// ...
protected $jobs;
public function __construct()
{
$this->jobs = new Doctrine\Common\Collections\ArrayCollection();
}
public function getJobs()
{
return $this->jobs;
}
public function setJobs($jobs)
{
$this->jobs = new Doctrine\Common\Collections\ArrayCollection();
foreach ($jobs as $job) {
$this->addJob($job);
}
return $this;
}
public function addJob(Job $job)
{
if (!$this->jobs->contains($job) {
$this->jobs->add($job);
// if needed for bi directional way
// $job->setBloc($this);
}
}
public function removeJob(Job $job)
{
// if you want
}
// ...
}
I have an entity, Offer, with a ManyToMany relationship to a Country Entity (i.e. an offer can be available on many countries)
Class Offer
{
[...]
/**
* #ORM\ManyToMany(targetEntity="Country")
* #ORM\JoinTable(
* name="offer_cc",
* joinColumns={
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="cc", referencedColumnName="cc")
* }
* )
*/
private $countries;
}
And I want to query for all offers which have a country of a given array.
An image to better understand:
In this case, It should show only Offer 1 and Offer 2 because the former has Andorra and the latter has Italy.
From the form I have an ArrayCollection of Country entities.
Is it possible to do this in a EntityRepository with a query builder?
For example, this is how I filter by payoutMode, which is a simple int value:
class OfferRepository extends EntityRepository
{
public function findAllFiltered(array $filter = [])
{
$qb = $this->createQueryBuilder('offer');
// Show only active offers
$qb->where('offer.status=1');
if($filter['payoutMode'] ?? null) {
$qb->andWhere("offer.payoutMode = :payoutMode")->setParameter(':payoutMode', $filter['payoutMode']);
}
// TODO add filter by cc, category, tags
return $qb->getQuery()->execute();
}
}
Here, $filter['countries'] contains:
ArrayCollection {#748 ▼
-elements: array:2 [▼
0 => Country {#762 ▼
-cc: "AD"
}
1 => Country {#769 ▼
-cc: "IT"
}
]
}
In DQL you there is IN function which can takes as right-side argument an array of IDs or array of entities. Use it for JOIN condition
Since you already get ArrayList of Country entities, it could be something like this:
class OfferRepository extends EntityRepository
{
public function findAllFiltered(array $filter = [])
{
$qb = $this->createQueryBuilder('offer');
// Show only active offers
$qb->where('offer.status=1');
if($filter['payoutMode'] ?? null) {
$qb->join("offer.payoutMode = :payoutMode")->setParameter(':payoutMode', $filter['countries']);
}
if(!empty($filter['payoutMode'])) {
$qb->join('offer.countries', 'c', Expr\Join::WITH, 'c IN :countries')
->setParameter(':countries', $filter['countries']);
}
// TODO add filter by cc, category, tags
return $qb->getQuery()->execute();
}
}
Code not tested, so I could mess something up with the DQL JOIN syntax.
I'm not sure if 'c IN :countries' syntax in valid in this context.
But in general, this is the way.
I think I found the solution:
if(count($filter['countries'] ?? [])) {
$qb->leftJoin('offer.countries', 'countries')
->andWhere("countries IN (:ccs)")->setParameter(':ccs', $filter['countries']);
}
This question is one in a series I seem to be generating as I slowly pick my way through learning testing.
I have a book model and a ticketaudit model. They have a relationship one to many. When a book is created a function should also create a range of tickets (for audit).
I want my test to make sure the ticketAudit model is being created and the association being made using the eloquent ORM within laravel.
my class so far:
Class TicketCreator implements TicketCreatorInterface {
protected $ticket;
public function __construct(TicketAudit $ticketAudit)
{
//dd($ticketAudit);
$this->ticket = $ticketAudit;
}
public function createTicket($input, $book) {
$counter = $input['start'];
while($counter <= $input['end']) {
$ticketDetails = array(
'ticketnumber'=>$counter,
'status'=>'unused',
'active'=>1
);
$this->ticket->create($ticketDetails)->save();
$this->ticket->book()->associate($book)->save();
$counter = $counter+1;
}
return $counter;
}
}
and my attempts at a test:
public function testCreateCreatesTickets() {
//arrange
$book = FactoryMuff::create('Book');
$aTicket = FactoryMuff::create('TicketAudit');
$ticketAudit = new TicketAudit;
$ticketCreator = new TicketCreator($ticketAudit);
//act
$response = $ticketCreator->createTicket(array('start'=>1000, 'end'=>1001), $book);
// Assert...
$this->assertEquals(true, $response);
}
However when I run this test I get the error:
Integrity constraint violation: 19 ticket_audits.ticketnumber may not be NULL
For some reason the model is not being created with the values I pass to it. I've checked in the function that the object exists and also the values are being created correctly in the array but it doesnt work.
Is this unique to testing?
I am creating an sqlite in memory database for this test.
Any help appreciated
Crikey this decision to start testing is a bit of a nightmare
Thanks to Manuel's request to post the TicketAudit class I noticed my model extended Eloquent. I had recently added Ardent and should have extended Ardent so the error lay in the model!
Revised corrected model :
use LaravelBook\Ardent\Ardent;
class TicketAudit extends Ardent {
protected $guarded = array();
public $autoHydrateEntityFromInput = true;
public $autoPurgeRedundantAttributes = true;
public static $rules = array(
'status' => 'required',
'ticketnumber' => 'required'
);
public static $factory = array(
'ticketnumber' => '1000',
'status' => 'unused',
);
public function book() {
return $this->belongsTo('Book');
}
}
Thank you for the sign post
i am writing webservice in symfony2 but i facing some problem regarding the output ,as it is giving blank output.
class DefaultController extends Controller {
/**
*
* #Route("/webservices/activity/{id}", name="user_json_activity")
* #Method("get")
*/
public function activityAction($id) {
$em = $this->getDoctrine()->getEntityManager();
$list = $em->getRepository('FitugowebserviceBundle:activity')->findOneById($id);
$r_array = $this->routes2Array($list);
$r = array('activity' => $r_array);
return new Response(json_encode($r));
}
private function routes2Array($routes) {
$points_array = array();
foreach ($routes as $route) {
$r_array = array('activity' => $route->getActivity(),
'icon' => $route->getIcon());
$points_array[] = $r_array;
}
return $points_array;
}
}
When i try to fetch data for id=1 http://domain.org/fitugo/web/app_dev.php/webservices/activity/1 it is giving output as follows
{"activity":[]}
It look very strange that you want get array with findOneById method. The first thing I suggest to add a check that the entity founded by id exist. Then look that findOneById returns and check your controller logic.
I have been getting into Unit Testing with Zend Framework. I am getting used to the other things it provide but I am having a hard time understanding Mock Objects.
For this example, I am trying to use a Mock Object to test out my model.
<?php
class Twitter_Model_Twitter
{
private $_twitter;
/**
* Make the options injectable.
* __contruct($auth, $key)
*/
public function __construct()
{
$config = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
$key = $config->encryption->salt;
$iv_size = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$password = mcrypt_decrypt(MCRYPT_XTEA, $key, $password, MCRYPT_MODE_ECB, $iv);
$this->_twitter = new Zend_Service_Twitter($username, $password);
}
public function verifyCredentials()
{
return $this->_twitter->account->verifyCredentials();
}
public function friendsTimeline($params)
{
return $this->_twitter->status->friendsTimeline($params);
}
}
For my unit test:
require_once ('../application/models/Twitter.php');
class Model_TwitterTest extends ControllerTestCase
{
/**
* #var Model_Twitter
*/
protected $_twitter;
public function testfriendsTimeline()
{
$mockPosts = array('foo', 'bar');
//my understanding below is:
//get a mock of Zend_Service_Twitter with the friendsTimeline method
$twitterMock = $this->getMock('Zend_Service_Twitter', array('friendsTimeline'));
/*
line above will spit out an error:
1) testfriendsTimeline(Model_TwitterTest)
Missing argument 1 for Mock_Zend_Service_Twitter_9fe2aeaa::__construct(), called in
/Applications/MAMP/bin/php5/lib/php/PHPUnit/Framework/TestCase.php on line 672 and
defined /htdocs/twitter/tests/application/models/TwitterTest.php:38
*/
$twitterMock->expects($this->once())
->method('friendsTimeline')
->will($this->returnValue($mockPosts));
$model = new Twitter_Model_Twitter();
$model->setOption('twitter', $twitterMock);
$posts = $model->friendsTimeline(array('count'=>20));
$this->assertEquals($posts, $mockPosts);
}
}
How would you test the following?
1) verifyCredentials()
2) friendsTimeline()
Thanks,
Wenbert
I am going to answer this question. I think I have made this work thanks to zomg from #zftalk.
Here is my new Twitter Model:
<?php
//application/models/Twitter.php
class Twitter_Model_Twitter
{
private $_twitter;
private $_username;
private $_password;
public function __construct(array $options = null)
{
if (is_array($options)) {
$this->setOptions($options);
$this->_twitter = new Zend_Service_Twitter($this->_username, $this->_password);
} else {
$twitterAuth = new Zend_Session_Namespace('Twitter_Auth');
$config = new Zend_Config_Ini(APPLICATION_INI, APPLICATION_ENV);
$key = $config->encryption->salt;
$iv_size = mcrypt_get_iv_size(MCRYPT_XTEA, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$password = mcrypt_decrypt(MCRYPT_XTEA, $key, $twitterAuth->password, MCRYPT_MODE_ECB, $iv);
$username = $twitterAuth->username;
$this->_twitter = new Zend_Service_Twitter($username, $password);
}
}
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$pieces = explode('_', $key);
foreach($pieces AS $piece_key => $piece_value) {
$pieces[$piece_key] = ucfirst($piece_value);
}
$name = implode('',$pieces);
$method = 'set' . $name;
//$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
//I added this method. So that I could "inject"/set the $_twitter obj
public function setTwitter($obj)
{
$this->_twitter = $obj;
return $this;
}
public function verifyCredentials()
{
return $this->_twitter->account->verifyCredentials();
}
public function friendsTimeline($params)
{
return $this->_twitter->status->friendsTimeline($params);
}
//in the real code, more will go here...
}
And in my Unit Test, I have this:
<?php
// tests/application/models/TwitterTest.php
require_once ('../application/models/Twitter.php');
class Model_TwitterTest extends ControllerTestCase
{
public function testVerifyCredentials()
{
$stub = $this->getMock('Zend_Service_Twitter', array('verifyCredentials'),array(),'',FALSE);
//FALSE is actually the 5th parameter to flag getMock not to call the main class. See Docs for this.
//Now that I have set the $_twitter variable to use the mock, it will not call the main class - Zend_Rest_Client (i think)
$stub->expects($this->once())
->method('verifyCredentials');
$model = new Twitter_Model_Twitter();
//this is the part where i set the $_twitter variable in my model to use the $stub
$model->setOptions(array('twitter'=>$stub));
$model->verifyCredentials();
}
}
Anyways, I think I got it working.
1) The unit test no longer tried to connect to twitter.com:80
2) After I got the setOptions() working in the Twitter_Model, $model->verifyCredentials() in my unit test was successfully called.
I will wait for others in Stackoverflow to confirm that is the right answer. For the meantime, would like to hear from you guys.
Thanks!!!