Im trying to make a custom validator to check if an email is already submited or not. for this I need to execute query in my custom validator, How can I do that?
use Phalcon\Validation\Validator,
Phalcon\Validation\ValidatorInterface,
Phalcon\Validation\Message;
Class Unique extends Validator implements ValidatorInterface {
public function validate($validator, $attribute) {
// how to execute "SELECT * FROM myTable" here...
}
}
If myTable is mapped to a Model you can just:
use Phalcon\Validation\Validator;
use Phalcon\Validation\ValidatorInterface;
use Phalcon\Validation\Message;
use MyTable;
class Unique extends Validator implements ValidatorInterface
{
public function validate($validator, $attribute)
{
$result = MyTable::findFirst("id = 1 AND status = 'sent'");
...
}
}
Related
I'm trying to create an SQLFilter for a query in my Symfony app.
The issue is that the filter is not applied on the query (and not called), even though is it enabled correctly (see below).
The repository is not linked to an entity, because the database is external to my app, but it still has access to the data.
Am I missing something ?
Here's the filter:
<?php
namespace App\SQL\Filter;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class UserRoleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
return 'c.roleId = 1';
}
}
I registered it in config/packages/doctrine.yaml:
doctrine:
filters:
user_role: App\SQL\Filter\UserRoleFilter
The controller:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\Persistence\ManagerRegistry;
use App\Repository\CustomerRepository;
class CustomerController extends AbstractController
{
public function myAction(Request $request, ManagerRegistry $doctrine, CustomerRepository $customerRepository)
{
$doctrine->getManager()->getFilters()->enable('user_role');
$customers = $customerRepository->findAll();
}
}
The repository:
<?php
namespace App\Repository;
use Doctrine\DBAL\Connection;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
class CustomerRepository
{
protected Connection $conn;
protected ObjectManager $em;
public function __construct(ManagerRegistry $doctrine)
{
$this->em = $doctrine->getManager();
$this->conn = $this->em->getConnection();
}
public function findAll(): array
{
dump($this->em->getFilters()->isEnabled('user_role')); // returns true
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->executeQuery()
->fetchAllAssociative();
}
}
From looking at the source for Doctrine/DBAL, it doesn't look like the filter would ever be applied to the query you are executing.
Within your repository class you are creating an instance of Doctrine\DBAL\Query\QueryBuilder which only holds a reference to Doctrine\DBAL\Connection.
Then the select data is set to its private parameter $sqlParts.
When executeQuery() is called is concatenates the content of sqlParts into a string with no mention or reference to any filter objects nor their constraints. This can be seen on line 308 of the QueryBuilder class.
QueryBuilder::executeQuery()
You can also see how the select query is concatenated on line 1320 of QueryBuilder.
QueryBuilder::getSQLForSelect()
The only way I can see to add it easily would be to add it directly to a where clause, e.g.
public function findAll(): array
{
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->where("c.roleId = 1") // Or pull it from the filter object in some way
->executeQuery()
->fetchAllAssociative();
}
If you want to see where the filter constraints are added to the queries you can find that data in the ORM package from Doctrine, however these are all linked to entities and table aliases.
SqlWalker::generateFilterConditionSQL()
BasicEntityPersistor::generateFilterConditionSQL()
ManyToManyPersistor::generateFilterConditionSQL()
I'm developing an application where my data comes from external server in JSON format.
I would like to set a relationships between each models, but without using a database table.
Is it possible ?
Something like that:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'https://.../server/flights.json';
}
You could make a service class which handles the request and returns class instances:
namespace App\Services;
class FlightService
{
/**
* #var FlightFactory
*/
private $flightFactory;
public function __construct(FlightFactory $flightFactory)
{
$this->flightFactory = $flightFactory;
}
public function getAllFlights()
{
$flightsJson = $this->getFromExternalCurl();
return $this->flightFactory->buildFlightList($flightsJson);
}
private function getFromExternalCurl()
{
return Curl::to('http://www.foo.com/flights.json')
->withData( array( 'foz' => 'baz' ) )
->asJson()
->get();
}
}
Basically the service would make the external API call and the response is passed to a factory which creates the instances.
Note that you just need to add the factory in the construct and it's binded because laravel uses https://laravel.com/docs/5.4/container
namespace App\Factories;
class FlightFactory
{
public function buildFlightList($flightJsonList)
{
$flightCollection = collect();
foreach($flightJsonList as $flightJson) {
$flightCollection->push($this->buildFlight($flightJson));
}
return $flightCollection;
}
public function buildFlight($flightJson)
{
$flight = new Flight();
// add properties
return $flight;
}
}
The factory will return a Collection which is verry usefull because it contains usefull methods, or you can return an array.
In this example I used a curl library https://github.com/ixudra/curl but it can be replaced with native php or other libraries.
Then you can use by injecting the FlightService in your controllers.
P.S: Code not tested but represents a possible approach
I have a line like this
// $repository is my repository for location data
$locationObject = $repository->findOneBy(array('name' => $locationName));
Which selects the first record it can find from the Locations table. Which is fair enough.
However, I have some additional data in that table to make the query more precise. Specifically, an "item_name" column. In the Location class it is specified as such:
/**
* #ORM\ManyToOne(targetEntity="Item", inversedBy="locations", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="item_id", referencedColumnName="item_id", onDelete="CASCADE")
*/
protected $item;
So there is also an Item table with item_id, item_name, etc.
What I want to do is change the original findOneBy() to also filter by item name. So I want something like:
$locationObject = $repository->findOneBy(array('name' => $locationName, 'item' => $itemName));
But because $item is an object in the Locations class rather than a string or an ID obviously that wouldn't work. So really I want to somehow much against item->getName()...
I'm not sure how I can do this. Does anyone have any suggestions?
Thanks
I guess you must create a custom query with join. It's better you create a custom repository class for this entity and then creates a custom query build inside it.
Entity:
// src/AppBundle/Entity/Foo.php
/**
* #ORM\Table(name="foo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FooRepository")
*/
class Foo
{
...
}
Your repository:
// src/AppBundle/Repository/FooRepository.php
use Doctrine\ORM\EntityRepository;
class FooRepository extends EntityRepository
{
public function findByYouWant($id)
{
// your query build
}
}
Controller:
// src/AppBundle/Controller/FooController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller
{
public function showAction()
{
// ... your code
$locationObject = $repository->findByYouWant($id);
}
}
You should add a method to your Location repository class, and create a query similiar to the one below:
class LocationRepository extends EntityRepository
{
public function findLocationByItemName($locationName, $itemName)
{
$qb = $this->createQueryBuilder('location');
$qb->select('location')
->innerJoin(
'MyBundle:Item',
'item',
Query\Expr\Join::WITH,
$qb->expr()->eq('location.item', 'item.item_id')
)
->where($qb->expr()->like('location.name', ':locationName'))
->andWhere($qb->expr()->like('item.name', ':itemName'))
->setParameter('locationName', $locationName)
->setParameter('itemName', $itemName);
$query = $qb->getQuery();
return $query->getResult();
}
}
You have to use a custom dql.You can construct it using the querybuilder.
//in your controller
protected function getEntities($itemName){
$em = $this->get('doctrine.orm.default_entity_manager');
$qb = $em->createQueryBuilder();
$qb->select('a')->from('YourBundleAlias:YourEntityName', 'a')->join('a.item','b')->where('b.item = :item')->setParameter('item', $itemName);
return $qb->getQuery()->execute();
}
This is as easy as:
$locationObject = $repository->findOneBy(array(
'name' => $locationName,
'item' => $itemObject
));
Using Doctrine2 in order to do a findBy on a related entity field you must supply an entity instance: $itemObject.
I want to replace the Laravels builder class with my own that's extending from it. I thought it would be as simple as matter of App::bind but it seems that does not work. Where should I place the binding and what is the proper way to do that in Laravel?
This is what I have tried:
my Builder:
use Illuminate\Database\Eloquent\Builder as BaseBuilder;
class Builder extends BaseBuilder
{
/**
* Find a model by its primary key.
*
* #param mixed $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|static|null
*/
public function find($id, $columns = array('*'))
{
Event::fire('before.find', array($this));
$result = parent::find($id, $columns);
Event::fire('after.find', array($this));
return $result;
}
}
And next I tried to register the binding in bootstrap/start.php file like this :
$app->bind('Illuminate\\Database\\Eloquent\\Builder', 'MyNameSpace\\Database\\Eloquent\\Builder');
return $app;
Illuminate\Database\Eloquent\Builder class is an internal class and as such it is not dependency injected into the Illuminate\Database\Eloquent\Model class, but kind of hard coded there.
To do what you want to do, I would extend the Illuminate\Database\Eloquent\Model to MyNamespace\Database\Eloquent\Model class and override newEloquentBuilder function.
public function newEloquentBuilder($query)
{
return new MyNamespace\Database\Eloquent\Builder($query);
}
Then alias MyNamespace\Database\Eloquent\Model to Eloquent at the aliases in app/config/app.php
Both of the answers are correct in some way. You have to decide what your goal is.
Change Eloquent Builder
For example, if you want to add a new method only for eloquent models (eg. something like scopes, but maybe a little more advanced so it’s not possible in a scope)
Create a new Class extending the Eloquent Builder, for Example CustomEloquentBuilder.
use Illuminate\Database\Eloquent\Builder;
class CustomEloquentBuilder extends Builder
{
public function myMethod()
{
// some method things
}
}
Create a Custom Model and overwrite the method newEloquentBuilder
use Namespace\Of\CustomEloquentBuilder;
use Illuminate\Database\Eloquent\Model;
class CustomModel extends Model
{
public function newEloquentBuilder($query)
{
return new CustomEloquentBuilder($query);
}
}
Change Database Query Builder
For example to modify the where-clause for all database accesses
Create a new Class extending the Database Builder, for Example CustomQueryBuilder.
use Illuminate\Database\Query\Builder;
class CustomQueryBuilder extends Builder
{
public function myMethod()
{
// some method things
}
}
Create a Custom Model and overwrite the method newBaseQueryBuilder
use Namespace\Of\CustomQueryBuilder;
use Illuminate\Database\Eloquent\Model;
class CustomModel extends Model
{
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new CustomQueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
}
Laravel Version: 5.5 / this code is untestet
The answer above doesn't exactly work for laravel > 5 so I done some digging and I found this!
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Database/Eloquent/Model.php#L1868
use this instead!
protected function newBaseQueryBuilder()
{
$conn = $this->getConnection();
$grammar = $conn->getQueryGrammar();
return new QueryBuilder($conn, $grammar, $conn->getPostProcessor());
}
I'm trying to return an object Contract and all of it's related Project. I can return all of the Contracts but when I try to get the contract's Project, I get a "Class 'EstimateProject' not found" error. I've run composer dump-autoload to reload the class mappings, but I still get the error. Any ideas? Here's my class setup:
EDIT: Just wanted to add that LaravelBook\Ardent\Ardent\ is an extension of Laravel's Model.php. It adds validation to model on the Save function. I've made Ardent extend another plugin I've added that is a MongoDB version of the Eloquent ORM.
EstimateContract.php
<?php namespace Test\Tools;
use LaravelBook\Ardent\Ardent;
class EstimateContract extends Ardent {
// This sets the value on the Mongodb plugin's '$collection'
protected $collection = 'Contracts';
public function projects()
{
return $this->hasMany('EstimateProject', 'contractId');
}
}
EstimateProject.php
<?php namespace Test\Tools;
use LaravelBook\Ardent\Ardent;
class EstimateProject extends Ardent {
// This sets the value on the Mongodb plugin's '$collection'
protected $collection = 'Projects';
public function contract()
{
return $this->belongsTo('EstimateContract', 'contractId');
}
}
EstimateContractController.php
<?php
use \Test\Tools\EstimateContract;
class EstimateContractsController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$contracts = EstimateContract::all();
echo $contracts;
foreach($contracts as $contract)
{
if($contract->projects)
{
echo $contract->projects;
}
}
}
}
In order for this to work, I needed to fully qualify the EstimateProject string in my EstimateContract model.
The solution was to change it from:
return $this->hasMany('EstimateProject', 'contractId');
to
return $this->hasMany('\Test\Tools\EstimateProject', 'contractId');
You have to use the fully qualified name, but I got the same error when I used forward slashes instead of back slashes:
//Correct
return $this->hasMany('Fully\Qualified\ClassName');
//Incorrect
return $this->hasMany('Fully/Qualified/ClassName');