I have written a custom function for the DQL:
<?php namespace Bundle\DQL\Functions;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
/**
* "DATE_COMPARE" "(" ArithmeticPrimary "," ComparisonOperator "," ArithmeticPrimary ")"
*/
class DateCompareFunction extends FunctionNode
{
public $date1;
public $date2;
public $operator;
/**
* #override
* #param SqlWalker $sqlWalker
* #return string
* #throws \Doctrine\DBAL\DBALException
*/
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'TRUNC(%s) %s TRUNC(%s)',
$this->date1->dispatch($sqlWalker),
$this->operator,
$this->date2->dispatch($sqlWalker)
);
}
/**
* #override
* #param Parser $parser
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->date1 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$this->operator = $parser->ComparisonOperator();
$parser->match(Lexer::T_COMMA);
$this->date2 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
And my where stmt looks like this:
$query = $em->createQueryBuilder()
->select('evt')
->from('Application\Model\Event', 'evt')
->where('evt.USR_ID in (:uid)')
->setParameter('uid', $usersId);
if (null !== $from) {
$query->andWhere('DATE_COMPARE(evt.DAY, >, TO_DATE(:from, \'yyyy-mm-dd\'))')
->setParameter('from', $from);
The problem is that Doctrine raise an exception for having a WHERE statement without comparison symbol:
object(Doctrine\ORM\Query\QueryException)[347]
protected 'message' => string '[Syntax Error] line 0, col 130: Error: Expected =, <, <=, <>, >, >=, !=, got ')'' (length=80)
private 'string' (Exception) => string '' (length=0)
protected 'code' => int 0
protected 'file' => string 'C:\Workspace\app\hcalendar\vendor\doctrine\orm\lib\Doctrine\ORM\Query\QueryException.php' (length=88)
protected 'line' => int 52
private 'trace' (Exception) =>
I have tried adding a stmt = TRUE, but the generated statement isn't understood by oracle, hwo can I do a where statement without any comparison symbol ? (just a true/false function return)
Why do you need this function? you can do the where condition without custom function, just write:
$query->andWhere('evt.day > :from')->setParameter('from', $from);
where the variable $from should be a DateTime object, and if you want the Oracle TRUNC function you can implement it by it self as in here https://github.com/ZeinEddin/ZeDoctrineExtensions/blob/master/lib/ZeDoctrineExtensions/Query/Oracle/TruncDate.php and just use it like this:
$query->andWhere('trunc(evt.day) > :from')->setParameter('from', $from);
If you want you can install this module for a ZF2 project and you will have the TruncDate function ready to be used in your project
Related
I'm migrating a project from Lumen 8 to Laravel 9. I'm having problems using Enums; however, it worked in Lumen.
$router->patch('/orcamentos/{id}/situacao', [
'uses' => AlterarSituacaoOrcamentoController::class . '#executar',
'middleware' => ['pode:' . Permissao::VENDA_ORCAMENTOS_ALTERAR]
]);
Enum
namespace App\Models\helpers\Enums;
enum SituacaoOrcamento: string
{
case Orcado = 'Orçado';
case EnviadoAoCliente = 'Enviado ao cliente';
case AprovadoPeloCliente = 'Aprovado pelo cliente';
case RejeitadoPeloCliente = 'Rejeitado pelo cliente';
case RejeitadoPelaEmpresa = 'Rejeitado pela empresa';
case ConvertidoEmVenda = 'Convertido em venda';
}
Test
public function testaRetorno(): void
{
$retorno = $this->patch('/orcamentos/1/situacao', [
'situacao' => SituacaoOrcamento::AprovadoPeloCliente
]);
$retorno->assertStatus(Response::HTTP_OK);
}
The error when running the test:
Excepted a scalar, or an array as a 2nd argument to
"Symfony\Component\HttpFoundation\InputBag::set()",
"App\Models\helpers\Enums\SituacaoOrcamento" given.
The errors happen before even entering the controller. It occurs on the method set of class InputBag from Symfony/Component/HttpFoundation.
/**
* Sets an input by name.
*
* #param string|int|float|bool|array|null $value
*/
public function set(string $key, mixed $value)
{
if (null !== $value && !is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) {
throw new \InvalidArgumentException(sprintf('Excepted a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
}
$this->parameters[$key] = $value;
}
If I comment this if in the package file, it works. Maybe I'm doing something wrong that causes the issue.
I'm trying the following: I have two models (Pub and Schedule) related by a 1xN relationship as follows:
Pub:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function pubSchedules()
{
return $this->hasMany(Schedule::class);
}
Schedule:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pub()
{
return $this->belongsTo(Pub::class);
}
Table schedules has the following fields:
id | pub_id | week_day | opening_time | closing_time |
I use the following function to know if one pub is currently (or not) open:
/**
* #return bool
*/
public function isPubCurrentlyOpen()
{
$schedules = Schedule::where([
['pub_id', $this->id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
In my PubController I'd like, when the option "Filter by open pubs" is chosen if($request->openPubs == 1), to show only opened pubs isOpen ==true.
Knowing the relationships between models, how can I do it?
I'm looking for something like this:
if($request->openPubs == 1)
{
$pubs = $pubs->with('pubSchedules')->where('isOpen' == true);
}
Can you help me?
Thanks a lot!
You can do this using a "whereHas"
$openPubs = Pub::whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
})->get();
This is assuming your opening time and closing time are the appropriate time format and not strings (though strings will work as well in a 24h format).
You might achieve something similar to what you are looking for by using a scope e.g.
public function scopeFilterBy($query, $filter = null) {
if ($filter == "isOpen") {
$query->whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
});
}
return $query; //Not sure if this is needed
}
You could then do:
Pub::filterBy($request->openPubs ? "isOpen" : null)->get();
I don't fully understand how you are trying to accomplish this but it should be something like this
$pubs = Pub::with(['pubSchedules' => function ($query) {
$query->where('opening_time', '>' ,Carbon::now()) // make sure it's currently open
->where('closing_time', '<' ,Carbon::now()) // make sure that it's not finished already
->where('week_day', '==' ,Carbon::now()->dayOfWeek) // make sure it's today
}])->find($id);
// to get if pub is currently
if($pub->pubSchedules->count()){
//
}
you can put this code in the model (Pub) and make some changes
if you already have the object you can do this (Add it to model)
public function isPubOpen()
{
$this->load(['pubSchedules' =>
// same code in other method
]);
return (bool) $this->pubSchedules->count();
}
For small tables you could call the function isPubCurrentlyOpen for each element.
For this you would need to change your function to recieve the pub_id as a parameter:
public function isPubCurrentlyOpen($pub_id)
{
$schedules = Schedule::where([
['pub_id', $pub_id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
and to query the data do:
if($request->openPubs == 1)
{
// assuming $pubs is a collection instance
$pubs = $pubs->filter(function($a){
return $this->isPubCurrentlyOpen($a->id);
})
}
There's a feature in Eloquent called Eager Loading. The Eloquent ORM provides a simple syntax to query for all the Schedules that are related with this particular Pub as described below:
$pubIsOpen= $pub->schedules()
->where([
['week_day', Carbon::now()->dayOfWeek],
['opening_time' , '<' , Carbon::now('Europe/Madrid')],
['closing_time' , '>' , Carbon::now('Europe/Madrid')]
])
->count();
if($openPubCount > 0){
//PUB is open
}else{
//PUB is closed
}
If it helps to someone in the future I post my solution, thanks to #apokryfos:
Pub:
/**
* #param $pubs
* #return mixed
*/
public static function isPubCurrentlyOpen($pubs)
{
$pubs->whereHas( 'pubSchedules', function ($pubs) {
$pubs->where( 'week_day', Carbon::now()->dayOfWeek )
->whereRaw(
"'" . Carbon::now( 'Europe/Madrid' )->format( "H:i:s" ) . "' BETWEEN opening_time AND closing_time"
);
} );
return $pubs;
}
PubsController:
/**
* #param GetPubRequest $request
* #return ApiResponse
*/
public function getPubs(GetPubRequest $request)
{
$orderBy = 'id';
$order = 'asc';
$pubs = Pub::withDistance();
............
if($request->openPubs == 1)
{
$pubs = Pub::isPubCurrentlyOpen($pubs);
}
return $this->response(PubProfileResource::collection($pubs->orderBy($orderBy, $order)->paginate()));
}
I´d like to use a variable in a typo3 flow repository. With
$letter = $_POST['someVariable'];
it works with my following Repository:
public function findLetter() {
$letter = $_POST['letter'];
$query = $this->createQuery();
$query->matching(
$query->like('name', $letter)
);
return $query->execute();
}
I read that it should also be possible in typo3 flow to get variables by
$letter = $this->request->getArgument('someVariable');
but this doesn´t work for me; I get the following error:
#1: Notice: Undefined property: ......\Domain\Repository\MitgliedRepository::$request in /var/www/apps/flow/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/..._..._Domain_Repository_...Repository.php line 96
line 96 in ...Repository is that:
$letter = $this->request->getArgument('letter');
Does anybody know, what I´m doing wrong?
I got it:
My Controller now looks linke that:
/**
* #return void
* #param string $letter
*/
public function letterAction($letter) {
$this->view->assign('mitglieder', $this->mitgliedRepository->findLetter($letter));
}
And my Repository looks like that:
/**
* #return string
*/
public function findLetter($letter) {
$query = $this->createQuery();
$query->matching(
$query->like('name', $letter)
)
->setOrderings(array('name' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING));
return $query->execute();
}
Sometimes happen that a collection of values is stored in a database as a unique string. This string is so made of all the values separated by a user-defined delimiter (e.g. "," or "_").
What would be nice in a Symfony2.1 application is to have a Validation Constraint that validates a string (e.g. provided by an input text form) by counting the number of tokens included in that string.
A possible example is when you store the tags in a string format, i.e. you receive a string from an input field like value1,value2,value10,value25. You see that 4 tokens are passed, but there is no form validator that does that control for you. So, one should use such a validator like:
/**
* #Assert\Token(
* delimiter=",",
* min = "1",
* max = "5",
* minMessage = "You must specify at least one token",
* maxMessage = "You cannot specify more than 5 tokens")
*/
$tags;
There is something similar when using the new in Symfony2.1 Count validator, but is doesn't work on strings, just on array of objects that implements Countable.
Who know how to implement that kind of "tokenized string" validator?
I solved my problem, I just want to share my solutions.
One possible solution is to use a Callback constraint. For instance, following the tag list example provided in the question:
/**
* #Assert\Callback(methods={"isTagStringValid"})
*/
class AFormModel{
protected $tags;
public function isTagStringValid(ExecutionContext $context){
$tagsExploded = explode(',', $this->tags);
if(count($tagsExploded)==0){
$context->addViolationAtSubPath('tags', 'Insert at least a tag', array(), null);
}
if(count($tagsExploded)==1 && $tagsExploded[0]==='')
$context->addViolationAtSubPath('tags', 'Insert at least a tag', array(), null);
}
else if(count($tagsExploded)>10){
$context->addViolationAtSubPath('tags', 'Max 10 values', array(), null);
}
}
}
A more elegant way is to define the "Token" validator. An example follows here:
namespace .....
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class Token extends Constraint {
public $min;
public $max;
public $minMessage = '{{ min }} token(s) are expected';
public $maxMessage = '{{ max }} token(s) are expected';
public $invalidMessage = 'This value should be a string.';
public $delimiter = ',';
public function __construct($options = null){
parent::__construct($options);
if (null === $this->min && null === $this->max) {
throw new MissingOptionsException('Either option "min" or "max" must be given for constraint ' . __CLASS__, array('min', 'max'));
}
}
}
And the validator class is:
namespace ...
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class TokenValidator extends ConstraintValidator {
public function isValid($value, Constraint $constraint) {
if ($value === null) {
return;
}
if(!is_string($value)){
$this->context->addViolation($constraint->invalidMessage, array(
'{{ value }}' => $value,
));
return;
}
$tokensExploded = explode($constraint->delimiter, $value);
$tokens = count($tokensExploded);
if($tokens==1){
if($tokensExploded[0]==='')
$tokens = 0;
}
if (null !== $constraint->max && $tokens > $constraint->max) {
$this->context->addViolation($constraint->maxMessage, array(
'{{ value }}' => $value,
'{{ limit }}' => $constraint->max,
));
return;
}
if (null !== $constraint->min && $tokens < $constraint->min) {
$this->context->addViolation($constraint->minMessage, array(
'{{ value }}' => $value,
'{{ limit }}' => $constraint->min,
));
}
}
}
In this way you can import the user-defined validator and use it everywhere like I proposed in my question.
I'm having problems using find*() magic methods of Doctrine2 when the field has an underscore in between.
$repository->findByName("Hello"); // Works
$repository->findByIsEnabled(true);
Entity 'Acme\SecurityBundle\Entity\Package' has no field 'isEnabled'.
You can therefore not call 'findByIsEnabled' on the entities'
repository.
This is the simple entity definition in YAML for replicating the error:
Acme\SecurityBundle\Entity\Package:
type: entity
repositoryClass: Acme\SecurityBundle\Repository\PackageRepository
table: security_package
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
length: 255
unique: true
is_enabled:
type: boolean
I recall having had the same problem and think I solved it by writing something like this :
$repository->findBy(array('is_enabled' => true));
Let's look at the code :
<?php
/**
* Adds support for magic finders.
*
* #return array|object The found entity/entities.
* #throws BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
public function __call($method, $arguments)
{
if (substr($method, 0, 6) == 'findBy') {
$by = substr($method, 6, strlen($method));
$method = 'findBy';
} else if (substr($method, 0, 9) == 'findOneBy') {
$by = substr($method, 9, strlen($method));
$method = 'findOneBy';
} else {
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
}
if ( !isset($arguments[0])) {
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
throw ORMException::findByRequiresParameter($method.$by);
}
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
return $this->$method(array($fieldName => $arguments[0]));
} else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
}
}
The key line is here:
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
Now let's have a look to classify :
<?php
/**
* Convert a word in to the format for a Doctrine class name. Converts 'table_name' to 'TableName'
*
* #param string $word Word to classify
* #return string $word Classified word
*/
public static function classify($word)
{
return str_replace(" ", "", ucwords(strtr($word, "_-", " ")));
}
It looks like you're supposed to write your fields "likeThis" if you want this to work.