improve SearchQuery API readability by deprecating functions

deprecate filter, exclude, fuzzysearch, inClass

deprecate start, limit

deprecate page, update SearchQuery_Range

add _config.php with Deprecation versioning

update index.md method names

update solr.md method names
This commit is contained in:
Andrew Aitken-Fincham 2018-05-14 17:04:40 +01:00
parent 65204542c0
commit e2a68ccffe
11 changed files with 259 additions and 59 deletions

5
_config.php Normal file
View File

@ -0,0 +1,5 @@
<?php
use SilverStripe\Dev\Deprecation;
Deprecation::notification_version('3.0', 'silverstripe/fulltextsearch');

View File

@ -262,13 +262,13 @@ use SilverStripe\FullTextSearch\Search\Queries;
$index = new MyIndex(); $index = new MyIndex();
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My Term'); $query->addSearchTerm('My Term');
$params = [ $params = [
'spellcheck' => 'true', 'spellcheck' => 'true',
'spellcheck.collate' => 'true', 'spellcheck.collate' => 'true',
]; ];
$results = $index->search($query, -1, -1, $params); $results = $index->search($query, -1, -1, $params);
$results->spellcheck $results->spellcheck;
``` ```
The built-in `_text` data is better than nothing, but also has some problems: The built-in `_text` data is better than nothing, but also has some problems:
@ -337,10 +337,8 @@ use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->classes = [ $query->addClassFilter(Page::class);
['class' => Page::class, 'includeSubclasses' => true], $query->addSearchTerm('someterms', [SiteTree::class . '_Title', SiteTree::class . '_Content']);
];
$query->search('someterms', [SiteTree::class . '_Title', SiteTree::class . '_Content']);
$result = singleton(SolrSearchIndex::class)->search($query, -1, -1); $result = singleton(SolrSearchIndex::class)->search($query, -1, -1);
// the request to Solr would be: // the request to Solr would be:
@ -366,10 +364,8 @@ use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->classes = [ $query->addClassFilter(Page::class);
['class' => 'Page', 'includeSubclasses' => true], $query->addSearchTerm('Lorem', null, [SiteTree::class . '_Content' => 2]);
];
$query->search('Lorem', null, [SiteTree::class . '_Content' => 2]);
$result = singleton(SolrSearchIndex::class)->search($query, -1, -1); $result = singleton(SolrSearchIndex::class)->search($query, -1, -1);
// the request to Solr would be: // the request to Solr would be:
@ -463,7 +459,7 @@ use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$index = new MyIndex(); $index = new MyIndex();
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My Term'); $query->addSearchTerm('My Term');
$results = $index->search($query, -1, -1, ['hl' => 'true']); $results = $index->search($query, -1, -1, ['hl' => 'true']);
``` ```
@ -486,7 +482,7 @@ use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$index = new MyIndex(); $index = new MyIndex();
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My Term'); $query->addSearchTerm('My Term');
$results = $index->search($query, -1, -1, [ $results = $index->search($query, -1, -1, [
'facet' => 'true', 'facet' => 'true',
'facet.field' => 'SiteTree_ClassName', 'facet.field' => 'SiteTree_ClassName',
@ -698,8 +694,8 @@ In order to query the field, reverse the search conditions and exclude the range
```php ```php
// Wrong: Filter will ignore all empty field values // Wrong: Filter will ignore all empty field values
$myQuery->filter('fieldname', new SearchQuery_Range('*', 'somedate')); $myQuery->addFilter('fieldname', new SearchQuery_Range('*', 'somedate'));
// Better: Exclude the opposite range // Better: Exclude the opposite range
$myQuery->exclude('fieldname', new SearchQuery_Range('somedate', '*')); $myQuery->addExclude('fieldname', new SearchQuery_Range('somedate', '*'));
``` ```

View File

@ -85,7 +85,7 @@ Note: There's usually a connector-specific "reindex" task for this.
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My house is on fire'); $query->addSearchTerm('My house is on fire');
``` ```
4). Apply that query to an index 4). Apply that query to an index
@ -123,7 +123,7 @@ class PageController extends ContentController
public function search(HTTPRequest $request) public function search(HTTPRequest $request)
{ {
$query = new SearchQuery(); $query = new SearchQuery();
$query->search($request->getVar('q')); $query->addSearchTerm($request->getVar('q'));
return $this->renderWith([ return $this->renderWith([
'SearchResult' => singleton(MyIndex::class)->search($query) 'SearchResult' => singleton(MyIndex::class)->search($query)
]); ]);
@ -178,13 +178,13 @@ Manual updates are connector specific, please check the connector docs for detai
## Searching Specific Fields ## Searching Specific Fields
By default, the index searches through all indexed fields. By default, the index searches through all indexed fields.
This can be limited by arguments to the `search()` call. This can be limited by arguments to the `addSearchTerm()` call.
```php ```php
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My house is on fire', [Page::class . '_Title']); $query->addSearchTerm('My house is on fire', [Page::class . '_Title']);
// No results, since we're searching in title rather than page content // No results, since we're searching in title rather than page content
$results = singleton(MyIndex::class)->search($query); $results = singleton(MyIndex::class)->search($query);
``` ```
@ -201,9 +201,9 @@ use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range;
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My house is on fire'); $query->addSearchTerm('My house is on fire');
// Only include documents edited in 2011 or earlier // Only include documents edited in 2011 or earlier
$query->filter(Page::class . '_LastEdited', new SearchQuery_Range(null, '2011-12-31T23:59:59Z')); $query->addFilter(Page::class . '_LastEdited', new SearchQuery_Range(null, '2011-12-31T23:59:59Z'));
$results = singleton(MyIndex::class)->search($query); $results = singleton(MyIndex::class)->search($query);
``` ```
@ -220,9 +220,9 @@ The `SearchQuery` API has the concept of a "missing" and "present" field value f
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('My house is on fire'); $query->addSearchTerm('My house is on fire');
// Needs a value, although it can be false // Needs a value, although it can be false
$query->filter(Page::class . '_ShowInMenus', SearchQuery::$present); $query->addFilter(Page::class . '_ShowInMenus', SearchQuery::$present);
$results = singleton(MyIndex::class)->search($query); $results = singleton(MyIndex::class)->search($query);
``` ```
@ -300,7 +300,7 @@ Example:
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = new SearchQuery(); $query = new SearchQuery();
$query->search( $query->addSearchTerm(
'My house is on fire', 'My house is on fire',
null, null,
[ [

View File

@ -2,6 +2,7 @@
namespace SilverStripe\FullTextSearch\Search\Queries; namespace SilverStripe\FullTextSearch\Search\Queries;
use SilverStripe\Dev\Deprecation;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
use stdClass; use stdClass;
@ -48,7 +49,7 @@ class SearchQuery extends ViewableData
* @param array $boost Map of composite field names to float values. The higher the value, * @param array $boost Map of composite field names to float values. The higher the value,
* the more important the field gets for relevancy. * the more important the field gets for relevancy.
*/ */
public function search($text, $fields = null, $boost = []) public function addSearchTerm($text, $fields = null, $boost = [])
{ {
$this->search[] = [ $this->search[] = [
'text' => $text, 'text' => $text,
@ -56,18 +57,19 @@ class SearchQuery extends ViewableData
'boost' => $boost, 'boost' => $boost,
'fuzzy' => false 'fuzzy' => false
]; ];
return $this;
} }
/** /**
* Similar to {@link search()}, but uses stemming and other similarity algorithms * Similar to {@link addSearchTerm()}, but uses stemming and other similarity algorithms
* to find the searched terms. For example, a term "fishing" would also likely find results * to find the searched terms. For example, a term "fishing" would also likely find results
* containing "fish" or "fisher". Depends on search implementation. * containing "fish" or "fisher". Depends on search implementation.
* *
* @param string $text See {@link search()} * @param string $text See {@link addSearchTerm()}
* @param array $fields See {@link search()} * @param array $fields See {@link addSearchTerm()}
* @param array $boost See {@link search()} * @param array $boost See {@link addSearchTerm()}
*/ */
public function fuzzysearch($text, $fields = null, $boost = []) public function addFuzzySearchTerm($text, $fields = null, $boost = [])
{ {
$this->search[] = [ $this->search[] = [
'text' => $text, 'text' => $text,
@ -75,60 +77,131 @@ class SearchQuery extends ViewableData
'boost' => $boost, 'boost' => $boost,
'fuzzy' => true 'fuzzy' => true
]; ];
return $this;
} }
public function inClass($class, $includeSubclasses = true) /**
* @return array
*/
public function getSearchTerms()
{
return $this->search;
}
/**
* @param string $class
* @param bool $includeSubclasses
* @return $this
*/
public function addClassFilter($class, $includeSubclasses = true)
{ {
$this->classes[] = [ $this->classes[] = [
'class' => $class, 'class' => $class,
'includeSubclasses' => $includeSubclasses 'includeSubclasses' => $includeSubclasses
]; ];
return $this;
} }
/** /**
* Similar to {@link search()}, but typically used to further narrow down * @return array
*/
public function getClassFilters()
{
return $this->classes;
}
/**
* Similar to {@link addSearchTerm()}, but typically used to further narrow down
* based on other facets which don't influence the field relevancy. * based on other facets which don't influence the field relevancy.
* *
* @param string $field Composite name of the field * @param string $field Composite name of the field
* @param mixed $values Scalar value, array of values, or an instance of SearchQuery_Range * @param mixed $values Scalar value, array of values, or an instance of SearchQuery_Range
*/ */
public function filter($field, $values) public function addFilter($field, $values)
{ {
$requires = isset($this->require[$field]) ? $this->require[$field] : []; $requires = isset($this->require[$field]) ? $this->require[$field] : [];
$values = is_array($values) ? $values : [$values]; $values = is_array($values) ? $values : [$values];
$this->require[$field] = array_merge($requires, $values); $this->require[$field] = array_merge($requires, $values);
return $this;
} }
/** /**
* Excludes results which match these criteria, inverse of {@link filter()}. * @return array
*/
public function getFilters()
{
return $this->require;
}
/**
* Excludes results which match these criteria, inverse of {@link addFilter()}.
* *
* @param string $field * @param string $field
* @param mixed $values * @param mixed $values
*/ */
public function exclude($field, $values) public function addExclude($field, $values)
{ {
$excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : []; $excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : [];
$values = is_array($values) ? $values : [$values]; $values = is_array($values) ? $values : [$values];
$this->exclude[$field] = array_merge($excludes, $values); $this->exclude[$field] = array_merge($excludes, $values);
return $this;
} }
public function start($start) /**
* @return array
*/
public function getExcludes()
{
return $this->exclude;
}
public function setStart($start)
{ {
$this->start = $start; $this->start = $start;
return $this;
} }
public function limit($limit) /**
* @return int
*/
public function getStart()
{
return $this->start;
}
public function setLimit($limit)
{ {
$this->limit = $limit; $this->limit = $limit;
return $this;
} }
public function page($page) /**
* @return int
*/
public function getLimit()
{ {
$this->start = $page * self::$default_page_size; return $this->limit;
$this->limit = self::$default_page_size;
} }
public function isfiltered() public function setPageSize($page)
{
$this->setStart($page * self::$default_page_size);
$this->setLimit(self::$default_page_size);
return $this;
}
/**
* @return int
*/
public function getPageSize()
{
return (int) ($this->getLimit() / $this->getStart());
}
/**
* @return bool
*/
public function isFiltered()
{ {
return $this->search || $this->classes || $this->require || $this->exclude; return $this->search || $this->classes || $this->require || $this->exclude;
} }
@ -137,4 +210,84 @@ class SearchQuery extends ViewableData
{ {
return "Search Query\n"; return "Search Query\n";
} }
/**
* @codeCoverageIgnore
* @deprecated
*/
public function search($text, $fields = null, $boost = [])
{
Deprecation::notice('4.0', 'Use addSearchTerm() instead');
return $this->addSearchTerm($text, $fields, $boost);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function fuzzysearch($text, $fields = null, $boost = [])
{
Deprecation::notice('4.0', 'Use addFuzzySearchTerm() instead');
return $this->addFuzzySearchTerm($text, $fields, $boost);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function inClass($class, $includeSubclasses = true)
{
Deprecation::notice('4.0', 'Use addClassFilter() instead');
return $this->addClassFilter($class, $includeSubclasses);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function filter($field, $values)
{
Deprecation::notice('4.0', 'Use addFilter() instead');
return $this->addFilter($field, $values);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function exclude($field, $values)
{
Deprecation::notice('4.0', 'Use addExclude() instead');
return $this->addExclude($field, $values);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function start($start)
{
Deprecation::notice('4.0', 'Use setStart() instead');
return $this->setStart($start);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function limit($limit)
{
Deprecation::notice('4.0', 'Use setLimit() instead');
return $this->setLimit($limit);
}
/**
* @codeCoverageIgnore
* @deprecated
*/
public function page($page)
{
Deprecation::notice('4.0', 'Use setPageSize() instead');
return $this->setPageSize($page);
}
} }

View File

@ -2,6 +2,8 @@
namespace SilverStripe\FullTextSearch\Search\Queries; namespace SilverStripe\FullTextSearch\Search\Queries;
use SilverStripe\Dev\Deprecation;
/** /**
* Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly * Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly
* open ended) range * open ended) range
@ -17,18 +19,40 @@ class SearchQuery_Range
$this->end = $end; $this->end = $end;
} }
public function start($start) public function setStart($start)
{ {
$this->start = $start; $this->start = $start;
return $this;
} }
public function end($end) public function setEnd($end)
{ {
$this->end = $end; $this->end = $end;
return $this;
} }
public function isfiltered() public function isFiltered()
{ {
return $this->start !== null || $this->end !== null; return $this->start !== null || $this->end !== null;
} }
/**
* @deprecated
* @codeCoverageIgnore
*/
public function start($start)
{
Deprecation::notice('4.0', 'Use setStart() instead');
return $this->setStart($start);
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function end($end)
{
Deprecation::notice('4.0', 'Use setEnd() instead');
return $this->setEnd($end);
}
} }

View File

@ -5,6 +5,7 @@ namespace SilverStripe\FullTextSearch\Search\Variants;
use ReflectionClass; use ReflectionClass;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
use SilverStripe\FullTextSearch\Utils\CombinationsArrayIterator; use SilverStripe\FullTextSearch\Utils\CombinationsArrayIterator;
/** /**

View File

@ -4,6 +4,7 @@ namespace SilverStripe\FullTextSearch\Search\Variants;
use SilverStripe\Assets\File; use SilverStripe\Assets\File;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
use SilverStripe\FullTextSearch\Search\SearchIntrospection; use SilverStripe\FullTextSearch\Search\SearchIntrospection;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLSelect;
@ -100,6 +101,9 @@ class SearchVariantSubsites extends SearchVariant
* *
* A pull request has been raised for this issue. Once accepted this forked module can be deleted and the parent * A pull request has been raised for this issue. Once accepted this forked module can be deleted and the parent
* project should be used instead. * project should be used instead.
*
* @param SearchQuery $query
* @param SearchIndex $index
*/ */
public function alterQuery($query, $index) public function alterQuery($query, $index)
{ {
@ -108,7 +112,7 @@ class SearchVariantSubsites extends SearchVariant
} }
$subsite = $this->currentState(); $subsite = $this->currentState();
$query->filter('_subsite', [$subsite, SearchQuery::$missing]); $query->addFilter('_subsite', [$subsite, SearchQuery::$missing]);
} }
/** /**

View File

@ -72,7 +72,7 @@ class SearchVariantSubsiteTest extends SapphireTest
//apply the subsite filter on the query (for example, if it's passed into a controller and set before searching) //apply the subsite filter on the query (for example, if it's passed into a controller and set before searching)
//we've chosen an arbirary value of 2 here, to check if it is changed later //we've chosen an arbirary value of 2 here, to check if it is changed later
$query->filter('_subsite', 2); $query->addFilter('_subsite', 2);
$this->assertNotEmpty($query->require['_subsite']); $this->assertNotEmpty($query->require['_subsite']);
//apply the search variant's definition and query //apply the search variant's definition and query

View File

@ -141,7 +141,7 @@ class SolrIndexTest extends SapphireTest
$index->setService($serviceMock); $index->setService($serviceMock);
$query = new SearchQuery(); $query = new SearchQuery();
$query->search( $query->addSearchTerm(
'term', 'term',
null, null,
array('Field1' => 1.5, 'HasOneObject_Field1' => 3) array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
@ -181,7 +181,7 @@ class SolrIndexTest extends SapphireTest
$index->setService($serviceMock); $index->setService($serviceMock);
$query = new SearchQuery(); $query = new SearchQuery();
$query->search('term'); $query->addSearchTerm('term');
$index->search($query); $index->search($query);
} }
@ -218,7 +218,7 @@ class SolrIndexTest extends SapphireTest
// Search without highlighting // Search without highlighting
$query = new SearchQuery(); $query = new SearchQuery();
$query->search( $query->addSearchTerm(
'term', 'term',
null, null,
array('Field1' => 1.5, 'HasOneObject_Field1' => 3) array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
@ -227,7 +227,7 @@ class SolrIndexTest extends SapphireTest
// Search with highlighting // Search with highlighting
$query = new SearchQuery(); $query = new SearchQuery();
$query->search( $query->addSearchTerm(
'term', 'term',
null, null,
array('Field1' => 1.5, 'HasOneObject_Field1' => 3) array('Field1' => 1.5, 'HasOneObject_Field1' => 3)

View File

@ -5,6 +5,7 @@ namespace SilverStripe\FullTextSearch\Tests;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\FullTextSearch;

View File

@ -3,11 +3,13 @@
namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest; namespace SilverStripe\FullTextSearch\Tests\SolrReindexTest;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
/** /**
* Dummy variant that selects items with field Varient matching the current value * Dummy variant that selects items with field Variant matching the current value
* *
* Variant states are 0 and 1, or null if disabled * Variant states are 0 and 1, or null if disabled
*/ */
@ -28,9 +30,9 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly
self::disable(); self::disable();
self::$current = 0; self::$current = 0;
self::$variants = array( self::$variants = [
self::class => singleton(self::class) self::class => singleton(self::class)
); ];
} }
/** /**
@ -40,8 +42,8 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly
{ {
self::$current = null; self::$current = null;
self::$variants = null; self::$variants = null;
self::$class_variants = array(); self::$class_variants = [];
self::$call_instances = array(); self::$call_instances = [];
} }
public function activateState($state) public function activateState($state)
@ -68,29 +70,43 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly
{ {
// Always use string values for states for consistent json_encode value // Always use string values for states for consistent json_encode value
if (isset(self::$current)) { if (isset(self::$current)) {
return (string)self::$current; return (string) self::$current;
} }
} }
/**
* @param string $class
* @param SearchIndex $index
*/
public function alterDefinition($class, $index) public function alterDefinition($class, $index)
{ {
$self = get_class($this); $self = get_class($this);
$this->addFilterField($index, '_testvariant', array( $this->addFilterField($index, '_testvariant', [
'name' => '_testvariant', 'name' => '_testvariant',
'field' => '_testvariant', 'field' => '_testvariant',
'fullfield' => '_testvariant', 'fullfield' => '_testvariant',
'base' => DataObject::getSchema()->baseDataClass($class), 'base' => DataObject::getSchema()->baseDataClass($class),
'origin' => $class, 'origin' => $class,
'type' => 'Int', 'type' => 'Int',
'lookup_chain' => array(array('call' => 'variant', 'variant' => $self, 'method' => 'currentState')) 'lookup_chain' => [
)); [
'call' => 'variant',
'variant' => $self,
'method' => 'currentState'
]
]
]);
} }
/**
* @param SearchQuery $query
* @param SearchIndex $index
*/
public function alterQuery($query, $index) public function alterQuery($query, $index)
{ {
// I guess just calling it _testvariant is ok? // I guess just calling it _testvariant is ok?
$query->filter('_testvariant', $this->currentState()); $query->addFilter('_testvariant', $this->currentState());
} }
public function appliesTo($class, $includeSubclasses) public function appliesTo($class, $includeSubclasses)