I am trying to solve problem of cheeking function in standart gii
I need to check if function exist in yii\base\Model
and if it exist, add prefix to this function
For example if you generate model with yii2\gii
you will have somthing like this
/**
* #return \yii\db\ActiveQuery
*/
public function getErrors()
{
return $this->hasMany(Error::className(), ['groupId' => 'id']);
}
I need to change function name when it generate to
/**
* #return \yii\db\ActiveQuery
*/
public function funky_key_getErrors()
{
return $this->hasMany(Error::className(), ['groupId' => 'id']);
}
I extend basics gii and rewrite function but it doesn't help
My code from generators\model\Generator, i think i need to check $relations in this function
protected function generateRelations()
{
\before basik yii code\
$relations = self::checkExistClass($relations);
return $relations;
}
private static function checkExistClass($relations)
{
foreach ($relations as $name => $relation) {
foreach ($relation as $functionName => $functionValue) {
$functionNameGet = 'get' . $functionName;
$directory = new Model;
if (method_exists($directory, $functionNameGet)) {
$relation['funky_key_' . $functionName] = $functionValue;
unset($relation[$functionName]);
}
}
}
return $relations;
}
Related
I cannot seem to apply filtering on all children defined in a tree model format with eager loading mechanism
Here is my model definition (works great):
class Section extends Model
{
[...]
/**
* #return HasOne
*/
public function parent()
{
return $this->hasOne(
self::class,
'Id',
'IdParent'
)->with('parent');
}
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children');
}
[...]
}
Now I want to filter out recursive based on a 'criteria object'
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with([
'children' => function ($query) use ($sectionCriteria) {
if ($sectionCriteria) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
}
]);
This works bot it is applied to the first level of the tree.
My question would be: Is there a way to pass an object to the children() relation so I can apply filters recursive (which would apply to all levels)?
Something like, let's say:
P.S: This is not possible since only a callback is accepted as a parameter
public function children($parameters)
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children'=>$parameters);
}
What I wouldn't want to use (with respect to SOLID principles):
Make a static class variable which holds criteria
A global variable of any kind
I also tried to retrieve children recursive (and apply filters) but ended up with more queries so Eloquent is preety well optimized sooo...
I used technique #1 (Make a static class variable) though I do not really like it but it works.
Model
/**
* #var null|SectionCriteria
*/
public static $childrenFilter = NULL; //This can be whatever you need since it's static
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with(['children'=>self::searchChild()]);
}
/**
* #return \Closure
*/
public function searchChild()
{
return function ($builder) {
if (Section::$childrenFilter) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
};
}
/**
* #param SectionCriteria $criteria
*/
public static function setChildSearch(SectionCriteria $criteria)
{
Section::$childrenFilter = $criteria;
}
/**
* Remove the search criteria filter
*/
public static function clearChildSearch()
{
Section::$childrenFilter = NULL;
}
Repository (the actual usage)
/**
* #param SectionCriteria|NULL $sectionCriteria
* #return Section[]|Collection
*/
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with(['children']); //Here I do not need the root elements to be filtered, If needed then use: Section::with(['children'=>Section::searchChild()])
Section::setChildSearch($sectionCriteria);
$builder->orderBy('Name');
$results = $builder->get();
Section::clearChildSearch();
return $results;
}
Again...not preety but it gets the job done
New: Another way (will test this out) would be to extend the Builder class
I have 2 foreign keys which are fk_author and fk_bookcase , I am trying to create my function edit() via a folder Repositorie but I am stuck on the syntax again.
Here is my code via the file BookRepository
public function edit($id)
{
$books = Book::find($id);
$authors = Author::all();
$bookcases = Bookcase::all();
return Book::find($id);
}
Then, in my Controller I have this...
public function edit($id)
{
$books = $this->books->edit($id);
return view('admin.books.edit', compact('books', 'authors', 'bookcases'));
}
Do you have an idea of the problem?
Regards
If you want to retrieve the book with the related 'author' and 'bookcase', you must have defined the relations in the models. For ex:
Book Model
public function author()
{
return $this->belongsTo(Author::class, 'fk_author'); // change fk_author for the key you are using
}
public function bookcase()
{
return $this->belongsTo(Bookcase::class, 'fk_bookcase');
}
Author Model
public function books()
{
return $this->hasMany(Book::class);
}
Bookcase Model
public function books()
{
return $this->hasMany(Book::class);
}
And you doesn't need and edit() function in your repository, just a detail() (or the name what you want) which retrive the Book Object with the relations.
BookRepository
public function detail($id)
{
return Book::with([
'author',
'bookcase',
])
->find($id);
}
Then, in the Controller, yes, you have an edit function which get the detail from the repository and return the object to the edit view.
/**
* #var BookRepository
*/
private $books;
public function __construct(BookRepository $books)
{
$this->books = $books;
}
public function edit($id)
{
$book = $this->books->detail($id);
return view('admin.books.edit', compact('book'));
}
If in any case you want to also return all the authors and bookcases, I think it is better to make a repository for each one, so you can also use them from other Controllers or Classes.
AuthorRepository
public function getAll()
{
return Author::all();
}
BookcaseRepository
public function getAll()
{
return Bookcase::all();
}
Then, in the Controller
/**
* #var BookRepository
*/
private $books;
/**
* #var AuthorRepository
*/
private $authors;
/**
* #var BookcaseRepository
*/
private $bookcases;
public function __construct(BookRepository $books, AuthorRepository $authors, BookcaseRepository $bookcases)
{
$this->books = $books;
$this->authors = $authors;
$this->bookscases = $bookcases;
}
public function edit($id)
{
$book = $this->books->detail($id);
$authors = $this->authors->getAll();
$bookcases = $this->bookcases->getAll();
return view('admin.books.edit', compact('book', 'authors', 'bookcases'));
}
I want (for project reason), to create an array in a class controller and pass it to a resource.
Consider in my controller class this method:
public function getExample(){
$attribute=array('otherInfo'=>'info');
return new ExampleResource($attribute);
}
and I in my class I would write sominthing like ExampleResource with:
public function toArray($request){
return[
'info' => $this->info
];
}
How I can convert the value $attribute to perform this operation return new ExampleResource($attribute); ?
Please do not suggest me to insert the field info in the model, this attribute can came from only from the external, from the controller and do not belong to the model in database.
class ExampleResource extends Resource
{
private $info;
/**
*
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function __construct($info)
{
$this->$info = $info;
}
public function toArray($request)
{
return[
'info'=>$this->$info,
'id' => $this->id
];
}
}
Add constructor to the resource class:
public function __construct($resource, $attribute)
{
$this->resource = $resource;
$this->attribute = $attribute;
}
Then in toArray():
return [
'info' => $this->attribute,
'created' => $this->created_at
];
And use it:
return new ExampleResource(Model::find($id), $attribute);
Resources are intended to be used to easily transform your models into JSON.
Take a look at this example:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
You just want to return an array of data so you should just return the array, it will be automatically turned into JSON:
Route::get('/info', function () {
return ['info' => 'info ...'];
});
For more informations check the docs here
Suppose I have a query that returns the following data:
RangeId | MinValue | MaxValue | Resolution | UnitId | UnitName
I want to hydrate the object MeasurementRange with the above data.
class MeasurementRange {
public function getRangeId() {...};
public function setRangeId($id) {...};
public function getRange() {...};
public function setRange(Range $range) {...};
public function getUnit() {...};
public function setUnit(Unit $unit) {...};
}
class Range {
public function getMinValue() {...};
public function setMinValue(float $minVal) {...};
public function getMaxValue() {...};
public function setMaxValue(float $maxVal) {...};
public function getResolution {...};
public function setResolution(float $resolution) {...};
}
class Unit {
public function getUnitId() {...};
public function setUnitId(int $id) {...};
public function getUnitName() {...};
public function setUnitName(string $name) {...};
}
As you can see the MeasurementRange object has set Range and Unit objects.
How can I hydrate MeasurementRange and the inner Range and Unit objects from the above query?
PS: I didn't specify protected properties of the objects. I guess they are self-evident.
You need to create a mapper, that will use your dbAdapter to fetch the data as an array, and then use hydrators to hydrate all the objects, and finally add the Range and Unit to MeasurementRange. You could alternatively create a custom hydrator (that would be even better, in terms of single responsibility).
I haven't got time to clean the example below, but that's what it could look like :)
final class LanguageMapper
{
/**
* #param LanguageTable $languageTable
* #param PackageTable $packageTable
* #param Cache $cache
* #param LoggerInterface $logger
*/
public function __construct(LanguageTable $languageTable, PackageTable $packageTable, Cache $cache, LoggerInterface $logger)
{
$this->languageTable = $languageTable;
$this->packageTable = $packageTable;
$this->cache = $cache;
$this->logger = $logger;
}
/**
* #param array $where
*
* #return array List of active languages
*/
public function findActive(array $where = [])
{
try {
if (empty($where) && $this->cache->hasItem('active_languages')) {
return unserialize($this->cache->getItem('active_languages'));
}
} catch (RuntimeException $exception) {
$this->logger->critical($exception->getMessage(), [
'exception' => $exception,
'file' => $exception->getFile(),
'line' => $exception->getLine(),
]);
}
/* #var $adapter \Zend\Db\Adapter\Adapter */
$adapter = $this->languageTable->getGateway()->getAdapter();
$sql = new Sql($adapter);
$select = $sql->select()->columns([Select::SQL_STAR])
->from('language')
->join('package', 'package.id = language.package', Select::SQL_STAR, Select::JOIN_LEFT)
->where(array_merge($where, ['active' => true]))
->order(['position']);
$selectString = $sql->buildSqlString($select);
$resultSet = $adapter->query($selectString, Adapter::QUERY_MODE_EXECUTE);
$languages = [];
$hydrator = new ArraySerializable();
foreach ($resultSet as $result) {
$language = new Language();
$package = new Package();
$hydrator->hydrate((array) $result, $package);
$hydrator->hydrate((array) $result, $language);
$language->setPackage($package);
$languages[] = $language;
}
if (empty($where)) {
try {
$this->cache->setItem('active_languages', serialize($languages));
} catch (RuntimeException $exception) {
$this->logger->warning($exception->getMessage(), [
'exception' => $exception,
'file' => $exception->getFile(),
'line' => $exception->getLine(),
]);
}
}
return $languages;
}
}
I have 3 models: Image, Company and File. So if we look through Company model, we have:
/**
* #return \yii\db\ActiveQuery
*/
public function getImages()
{
return $this->hasMany('galleries\models\Image', ['id' => 'image_id'])
->viaTable('{{%companies_has_images}}', ['company_id' => 'id']);
}
public function extraFields()
{
return ['images'];
}
now an Image model:
/**
* #return \yii\db\ActiveQuery
*/
public function getFile()
{
return $this->hasOne('app\models\File', ['id' => 'file_id']);
}
public function extraFields()
{
return ['file'];
}
So here is the question, how can i get images with correct files in getImages() in the Company model?
You'll have to fetch the images first and then provide an extra getter function to return the files:
public function getImageFiles()
{
$files = [];
foreach ($this->images as $image)
$files[] = $image->file;
return $files;
}