I'm building a laravel app using Sentinel, based in an old system code in Yii.
Purpose is be able to login in new system with old users / old db.
I first has to resolved model issue:
Custom Model and fields with Sentinel / Laravel
Now, it is ok.
I have a last issue, it seems to be hashing password from different ways.
When I check the hash method in Yii, I can find that it use Blowfish algorithm:
/**
* CPasswordHelper provides a simple API for secure password hashing and verification.
*
* CPasswordHelper uses the Blowfish hash algorithm available in many PHP runtime
* environments through the PHP {#link http://php.net/manual/en/function.crypt.php crypt()}
* built-in function. As of Dec 2012 it is the strongest algorithm available in PHP
* and the only algorithm without some security concerns surrounding it. For this reason,
* CPasswordHelper fails to initialize when run in and environment that does not have
* crypt() and its Blowfish option. Systems with the option include:
* (1) Most *nix systems since PHP 4 (the algorithm is part of the library function crypt(3));
* (2) All PHP systems since 5.3.0; (3) All PHP systems with the
* {#link http://www.hardened-php.net/suhosin/ Suhosin patch}.
* For more information about password hashing, crypt() and Blowfish, please read
* the Yii Wiki article
* {#link http://www.yiiframework.com/wiki/425/use-crypt-for-password-storage/ Use crypt() for password storage}.
* and the
* PHP RFC {#link http://wiki.php.net/rfc/password_hash Adding simple password hashing API}.
*
* CPasswordHelper throws an exception if the Blowfish hash algorithm is not
* available in the runtime PHP's crypt() function. It can be used as follows
*
In the other hand, Sentinel manage several hash methods:
Native hasher
Bcrypt hasher
Callback hasher
Whirlpool hasher
SHA256 hasher
So, I guessed the common method was bcrypt, and in my Laravel model I did:
class Administrador extends EloquentUser {
protected $table = 'administrador';
protected $fillable = [];
protected $primaryKey = 'administradorid';
protected $loginNames = ['correo'];
protected $guarded = ['administradorid'];
protected $hidden = ['contrasena', 'remember_token'];
use SoftDeletes;
protected $dates = ['deleted_at'];
/**
* Set the Sentry User Model Hasher to be the same as the configured Sentry Hasher
*/
public static function boot()
{
parent::boot();
Sentinel::setHasher(new BcryptHasher);
}
}
So really, I don't really know what to do to solve it....
What you can do to support both systems at this time is create an implementation of the Cartalyst\Sentinal\Hashing\HasherInterface like this for example:
use Cartalyst\Sentinel\Hashing\HasherInterface;
class CombinedHasher implements HasherInterface
{
/**
* #var HasherInterface
*/
private $primary;
/**
* #var HasherInterface
*/
private $fallback;
/**
* #param HasherInterface $primary
* #param HasherInterface $fallback
*/
public function __construct(HasherInterface $primary, HasherInterface $fallback)
{
$this->primary = $primary;
$this->fallback = $fallback;
}
/**
* Hash the given value.
*
* #param string $value
* #return string
* #throws \RuntimeException
*/
public function hash($value)
{
return $this->primary->hash($value);
}
/**
* Checks the string against the hashed value.
*
* #param string $value
* #param string $hashedValue
* #return bool
*/
public function check($value, $hashedValue)
{
if ($this->primary->check($value, $hashedValue)) {
return true;
}
return $this->fallback->check($value, $hashedValue);
}
}
As you can see it takes two instances of the HasherInterface. So in this case you would inject the new implementation you want you use first and then create an implementation of the interface which implements the hashing algorithm Yii is using.
While checking the hash it will first use the new hashing algorithm. If this returns false it will also check using the fallback (Yii algorithm). To create hashes it will only use the new hashing algorithm. (You might want to change this for development however you should not develop using the production database anyways.)
So what you have to do next is create an implementation of the HasherInterface which will use the hashing algorithm Yii is using:
use Cartalyst\Sentinel\Hashing\HasherInterface;
class YiiHasher implements HasherInterface
{
/**
* Hash the given value.
*
* #param string $value
* #return string
* #throws \RuntimeException
*/
public function hash($value)
{
// You'll have to implement this
return yiiHasher($value);
}
/**
* Checks the string against the hashed value.
*
* #param string $value
* #param string $hashedValue
* #return bool
*/
public function check($value, $hashedValue)
{
// You'll have to implement this
return yiiHashChecker($value, $hashedValue);
}
}
You'll have to check whether Yii has a package for this or you'll have to check their source code to see how it works.
So to use this you would create an instance of the CombinedHasher like this:
use Cartalyst\Sentinel\Hashing\BcryptHasher;
use Namespace\For\Your\YiiHasher;
$primary = new BcryptHasher();
$fallback = new YiiHasher();
$hasher = new CombinedHasher($primary, $fallback);
Update 1: Extra info from the documentation
After actually reading through their documentation I noticed they also provide a CallbackHasher which might be less work to set up: https://cartalyst.com/manual/sentinel/2.0#callback-hasher
They also recommend using the NativeHasher over the BcryptHasher: https://cartalyst.com/manual/sentinel/2.0#native-hasher
Update 2: Where to set up
You could for example create them in app/Hashing. Then you'd have to make sure they have the namespace App\Hashing.
To set this up you can use your AppServiceProvider which is located in app/Providers/AppServiceProvider.php.
// Import the classes on the top
use App\Hashing\CombinedHasher;
use App\Hashing\YiiHasher;
use Cartalyst\Sentinel\Hashing\NativeHasher;
// In the AppServiceProvider class itself
public function boot()
{
$hasher = $this->app['Cartalyst\Sentinel\Hashing\HasherInterface'];
Sentinel::setHasher($hasher);
}
public function register()
{
$this->app->singleton('Cartalyst\Sentinel\Hashing\HasherInterface', function($app) {
$primary = new NativeHasher();
$secondary = new YiiHasher();
return new CombinedHasher($primary, $secondary);
});
}
Related
We are migrating applications from CakePHP 2.X but we need to implement our mobile API's before the migration. I have followed all the items I could find but they all seem to be for v5 or less. No matter what I do Hash::make() still results in a Bcrypt password.
I really want to 2 birds one stone with having this allow sha1() login and update to Bcrypt upon login but we havent implemented on CakePHP 2.x successfully. So I need to get the Hasher working or a workaround. I know I can just Hash manually in the model but that doesnt allow Auth to work.
Any help would be appreciated
app.php config file
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
//Illuminate\Hashing\HashServiceProvider::class,
App\Providers\CustomHashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
CustomHashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
CustomHasher.php
<?php
namespace App\Lib\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
/**
* Hash the given value.
*
* #param string $value
* #return array $options
* #return string
*/
public function make($value, array $options = array()) {
//I have custom encoding / encryption here//
//Define your custom hashing logic here//
return sha1(env('SEC_SALT').$value);
}
/**
* Check the given plain value against a hash.
*
* #param string $value
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
/**
* Check if the given hash has been hashed using the given options.
*
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
public function info($hashedValue): array {
return $hashedValue;
}
}
UPDATE
I refactored based on #Mdexp answer to this .... but I found out the Configs are ignored unless added in app.php on Lumen
New app.php
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\Sha1HashServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Sha1HashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class Sha1HashServiceProvider extends ServiceProvider {
public function register() {
//
}
public function boot() {
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new \App\Lib\Sha1Hash\Sha1Hasher;
});
}
}
Sha1Hasher.php
<?php
namespace App\Lib\Sha1Hash;
use Illuminate\Hashing\AbstractHasher;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use RuntimeException;
class Sha1Hasher extends AbstractHasher implements HasherContract {
public function __construct(array $options = []) {
}
public function make($value, array $options = []) {
$hash = sha1(env('SEC_SALT').$value);
if ($hash === false) {
throw new RuntimeException('Sha1 hashing not supported.');
}
return $hash;
}
public function check($value, $hashedValue, array $options = []) {
return ($this->make($value) == $hashedValue)?true:false;
}
public function needsRehash($hashedValue, array $options = array()): bool {
return false;
}
}
I would use the default HashServiceProvider and register a new driver into it. It would also make the switch back from sha1 to bcrypt even quicker once you completed the transitioning phase.
1) You have have to create a class which extends the Illuminate\Hashing\AbstractHasher or at least implements the Illuminate\Contracts\Hashing\Hasher. Take a look at the current Bcrypt driver implementation as a reference on GitHub.
The CustomHasher class you provided should work just fine as a driver, I would just rename it to avoid confusion with naming.
2) Now you can register the hash drivers in a service provider like:
public function boot()
{
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new YourCustomSha1Hasher();
});
}
3) Then in your config/hashing.php file, set the driver to 'sha1' (must be equal to the first parameter of the extend function call.
4) It should work straight out of the box, and to choose a different hashing driver, just change the config/hashing.php configuration file with the driver that you want to use for hashing.
Note: the whole code hasn't been tested, but I looked through the source code to come up with this solution that should work. Just comment anything isn't working as expected so I can fix my answer.
I am trying to switch stores programmatically. I used following code to achieve it:
/**
* #var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager
) {
$this->_storeManager = $storeManager;
}
and then:
$this->_storeManager->setCurrentStore('YOUR_STORE_ID');
as given in https://magento.stackexchange.com/a/173763/59686
But no success. Only default store is displayed(selected) on storefront.
I have also tried this url scheme http://mystoreurl.com/?___store=storeId but it only display the store with given id instead of switching the store completely, Means when i visit the main url (http:mystoreurl.com), it is again displaying default store. Is there any way to switch the store programmatically just like it is selected as default from admin.
Or is there any way to add some readymade widget to switch stores (Store Switcher). The theme I am using does not have this feature to auto populate store switcher as default Magento Luma theme provides.
you need to do more:
use Magento\Store\Model\Store;
use Magento\Framework\App\Http\Context as HttpContext;
use Magento\Store\Api\StoreCookieManagerInterface;
use Magento\Store\Api\StoreRepositoryInterface;
[...]
public function __construct
(
HttpContext $httpContext,
StoreCookieManagerInterface $storeCookieManager,
StoreRepositoryInterface $storeRepository
) {
$this->httpContext = $httpContext;
$this->storeCookieManager = $storeCookieManager;
$this->storeRepository = $storeRepository;
}
[...]
public function yourFunction(){
$store = $this->storeRepository->getActiveStoreByCode('YOUR_STORE_CODE');
$this->httpContext->setValue(Store::ENTITY, 'YOUR_STORE_CODE', 'DEFAULT_STORE_CODE');
$this->storeCookieManager->setStoreCookie($store);
}
For Magento 2.3 running Varnish + Amasty GeoIP Redirect I had to slightly adjust the above to get mine working (on top of the configuration involved with the extension) whilst store switching:
/**
* #param string $storeCode
* #param Magento\Store\Model\Store $store
* #return void
*/
private function switchStore(string $storeCode, \Magento\Store\Model\Store $store): void
{
/** #var Magento\Framework\App\Http\Context */
$this->httpContext->setValue(\Magento\Store\Model\Store::ENTITY, $storeCode, $storeCode);
/** #var Magento\Store\Api\StoreCookieManagerInterface */
$this->storeCookieManager->setStoreCookie($store);
/** #var Magento\Store\Model\StoreManagerInterface */
$this->storeManager->setCurrentStore($store);
}
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.
Question
Is it possible in Symfony 2.8+ / 3.x+ to dispatch event before starting entity validation?
Situation:
Let's say we have 100 entities, they have #LifeCycleCallbacks, they have #postLoad Event that do something, but the result of this is only used for valiation of Entity, in 99% of situations result of #postLoad is unimportant for system. So if we have hundrets or thousands of Entities fetched from DB there will be a lot of machine-cycles lose for unimportant data.
It would be nice to run some kind of event, that will run method, that will populate that data for that specific Entity, just before validations starts.
instead of:
$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
we could have:
$validator = $this->get('validator');
$errors = $validator->validate($entity);
And in validate() situation, preValidate() will be dispatched autmaticly as Event (of course with check if Entity does have such method).
CaseStudy:
I have a system that stores pages/subpages as entities. There can be 10 or 10000 pages/subpages
Pages/subpages can have file.
Entities stores only files names (becouse we can't store SplFileInfo - resource serialization restriction)
While Entity->file property is type of string, when I want to make it to be instance of File (so we can do validation of type File) I have something like:
/**
* #postLoad()
*/
public function postLoad()
{
//magicly we get $rootPath
$this->file = new File($rootPath . '/' . $this->file);
}
/**
* #prePersist()
* #preUpdate()
*/
public function preSave()
{
if ($this->file instance of File)
$this->file = $this->file->getFilename();
}
}
Ok, but postLoad() will CHANGE the property, and Doctrine will NOTICE that. So in next
$entityManager->flush()
ALL entities will be flushed - even if preSave() will change it back to be just string as it was before.
So if I have any other entity, let's say TextEntity, and I want to remove it
$entityManager->remove($textEntity);
$entityManager->flush();
All other Entities that are somehow changed (change was noticed by Doctrine), are flushed, no matter if value of file property is the same as in DB (and change was only temporary).
It will be flushed.
So we have hundrets, or thousends of pointless sql updates.
Btw.
1. ->flush($textEntity) will throw Exception, becouse ->remove($textEntity) have already "deleted" that entity.
2. Entity property ->file must be of type File for Assert/File, becouse FileValidator can only accept values of File or absolute-path-to-file.
But I will NOT store absolute-path-to-file, becouse it will be completly different on Dev, Stage, and Production environments.
This is problem that occured when I tried to make file uploading as it was described in Symfony cookbook http://symfony.com/doc/current/controller/upload_file.html.
My solution was to, in postLoad(), create File instance in property that is not Doctrine column, and is anoted to have assertion, etc.
That works, but the problem of useless postLoad()s stays, and i thought about events. That could be elastic, and very elegant solution - instead of controllers getting "fat".
Any one have better solution? Or know how to dispatch event if ->validate() happends?
Hello Voult,
Edit: first method is deprecated in symfony 3 as the thread op mentioned in a comment. Check the second method made for symfony 3.
Symfony 2.3+,Symfony < 3
What I do in this cases, since symfony and most other bundles are using parameters for service class definition, is to extend that service. Check the example below and for more information on extending services check this link
http://symfony.com/doc/current/bundles/override.html
First you need to add a some marker to your entities that require pre-validation. I usually use interfaces for stuff like this something like
namespace Your\Name\Space;
interface PreValidateInterface
{
public function preValidate();
}
After this you extend the validator service
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Validator;
class MyValidator extends Validator //feel free to rename this to your own liking
{
/**
* #inheritdoc
*/
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
return parent::validate($value, $groups, $traverse, $deep);
}
}
And final step, you need to add the class value parameter to your 'parameters' config block in config.yml, something like this:
parameters:
validator.class: Your\Name\Space\MyValidator
This is the basic idea. Now you can mix end match this idea with whatever you want to achieve. For instance instead of calling a method on the entity (I usually like to keep business logic outside of my entities), you can look for the interface and if it is there you can launch a pre.validate event with that entity on it, and use a listener to do the job. After that you can keep the result from parent::validate and also launch a post.validate event. You see where i'm going with this. You basically can do whatever you like now inside that validate method.
PS: The example above is the easy method. If you want to go the event route, the service extension will be harder, since you need to inject the dispatcher into it. Check the link I provided at the beginning to see the other way to extend a service and let me know if you need help with this.
For Symfony 3.0 -> 3.1
In this case they managed to make it hard and dirtier to extend
Step 1:
Create your own validator something like this:
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception;
use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class myValidator implements ValidatorInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
/**
* #param ValidatorInterface $validator
*/
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
/**
* Returns the metadata for the given value.
*
* #param mixed $value Some value
*
* #return MetadataInterface The metadata for the value
*
* #throws Exception\NoSuchMetadataException If no metadata exists for the given value
*/
public function getMetadataFor($value)
{
return $this->validator->getMetadataFor($value);
}
/**
* Returns whether the class is able to return metadata for the given value.
*
* #param mixed $value Some value
*
* #return bool Whether metadata can be returned for that value
*/
public function hasMetadataFor($value)
{
return $this->validator->hasMetadataFor($value);
}
/**
* Validates a value against a constraint or a list of constraints.
*
* If no constraint is passed, the constraint
* {#link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* #param mixed $value The value to validate
* #param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* #param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validate($value, $constraints = null, $groups = null)
{
//the code you are doing all of this for
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
//End of code
return $this->validator->validate($value, $constraints, $groups);
}
/**
* Validates a property of an object against the constraints specified
* for this property.
*
* #param object $object The object
* #param string $propertyName The name of the validated property
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validateProperty($object, $propertyName, $groups = null)
{
$this->validator->validateProperty($object, $propertyName, $groups);
}
/**
* Validates a value against the constraints specified for an object's
* property.
*
* #param object|string $objectOrClass The object or its class name
* #param string $propertyName The name of the property
* #param mixed $value The value to validate against the
* property's constraints
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
{
$this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups);
}
/**
* Starts a new validation context and returns a validator for that context.
*
* The returned validator collects all violations generated within its
* context. You can access these violations with the
* {#link ContextualValidatorInterface::getViolations()} method.
*
* #return ContextualValidatorInterface The validator for the new context
*/
public function startContext()
{
$this->validator->startContext();
}
/**
* Returns a validator in the given execution context.
*
* The returned validator adds all generated violations to the given
* context.
*
* #param ExecutionContextInterface $context The execution context
*
* #return ContextualValidatorInterface The validator for that context
*/
public function inContext(ExecutionContextInterface $context)
{
$this->validator->inContext($context);
}
}
Step 2:
Extend Symfony\Component\Validator\ValidatorBuilder something like this:
namespace Your\Name\Space;
use Symfony\Component\Validator\ValidatorBuilder;
class myValidatorBuilder extends ValidatorBuilder
{
public function getValidator()
{
$validator = parent::getValidator();
return new MyValidator($validator);
}
}
You need to override Symfony\Component\Validator\Validation. This is the ugly/dirty part, because this class is final so you can't extend it, and has no interface to implement, so you will have to pay attention to in on future versions of symfony in case backward compatibility is broken. It goes something like this:
namespace Your\Name\Space;
final class MyValidation
{
/**
* The Validator API provided by Symfony 2.4 and older.
*
* #deprecated use API_VERSION_2_5_BC instead.
*/
const API_VERSION_2_4 = 1;
/**
* The Validator API provided by Symfony 2.5 and newer.
*/
const API_VERSION_2_5 = 2;
/**
* The Validator API provided by Symfony 2.5 and newer with a backwards
* compatibility layer for 2.4 and older.
*/
const API_VERSION_2_5_BC = 3;
/**
* Creates a new validator.
*
* If you want to configure the validator, use
* {#link createValidatorBuilder()} instead.
*
* #return ValidatorInterface The new validator.
*/
public static function createValidator()
{
return self::createValidatorBuilder()->getValidator();
}
/**
* Creates a configurable builder for validator objects.
*
* #return ValidatorBuilderInterface The new builder.
*/
public static function createValidatorBuilder()
{
return new MyValidatorBuilder();
}
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
And last step overwrite the parameter validator.builder.factory.class in your config.yml:
parameters:
validator.builder.factory.class: Your\Name\Space\MyValidation
This is the least invasive way to do it, that i can find. Is not that clean and it could need some maintaining when you upgrade symfony to future versions.
Hope this helps, and happy coding
Alexandru Cosoi
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;
}
}
?>